package com.jdd.modules.sdkidentifyresultpushlog.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jdd.common.api.vo.RedisVo;
import com.jdd.common.api.vo.Result;
import com.jdd.common.api.vo.ResultVo;
import com.jdd.common.api.vo.WebSocketVo;
import com.jdd.common.constant.*;
import com.jdd.common.exception.JddBootException;
import com.jdd.common.util.*;
import com.jdd.config.websocket.WebSocketServer;
import com.jdd.modules.configcartype.entity.ConfigCarType;
import com.jdd.modules.configcartype.service.IConfigCarTypeService;
import com.jdd.modules.operationLog.service.IParkHandOverLogService;
import com.jdd.modules.order.entity.ParkOrderInfo;
import com.jdd.modules.order.mapper.ParkOrderInfoMapper;
import com.jdd.modules.parkCarAbnormalRecord.enums.AbnormalTypeEnums;
import com.jdd.modules.parkCarAbnormalRecord.service.IParkCarAbnormalRecordService;
import com.jdd.modules.parkcar.entity.*;
import com.jdd.modules.parkcar.mapper.ParkCarStoredMapper;
import com.jdd.modules.parkcar.mapper.ParkInnerListMapper;
import com.jdd.modules.parkcar.mapper.ParkWhiteListMapper;
import com.jdd.modules.parkcar.service.IParkCarMonthlyService;
import com.jdd.modules.parkcar.service.IParkCarOutAbnormalLogService;
import com.jdd.modules.parkcar.service.IParkSpaceService;
import com.jdd.modules.parking.entity.*;
import com.jdd.modules.parking.mapper.ParkCarEnterLogMapper;
import com.jdd.modules.parking.mapper.ParkCarOutLogMapper;
import com.jdd.modules.parking.service.*;
import com.jdd.modules.parkmonthly.entity.MonthlyCarConfig;
import com.jdd.modules.parkmonthly.service.IMonthlyCarConfigService;
import com.jdd.modules.parkmonthly.service.IMonthlyCarTypeConvertService;
import com.jdd.modules.parkmonthly.service.IMonthlyCardInfoService;
import com.jdd.modules.parkmonthly.service.IMonthlyPeromissionGroupService;
import com.jdd.modules.sdk.LedCallInterface;
import com.jdd.modules.sdk.SdkCallInterface;
import com.jdd.modules.sdkidentifyresultpushlog.dto.ParkGetCarFeeLogV3DTO;
import com.jdd.modules.sdkidentifyresultpushlog.entity.*;
import com.jdd.modules.sdkidentifyresultpushlog.entity.enums.EnterTypeNoPermissionKind;
import com.jdd.modules.sdkidentifyresultpushlog.entity.enums.InOutTypeKind;
import com.jdd.modules.sdkidentifyresultpushlog.entity.enums.IsOpenKind;
import com.jdd.modules.sdkidentifyresultpushlog.entity.enums.MonthlyEffectiveDaysKind;
import com.jdd.modules.sdkidentifyresultpushlog.entity.vo.MonthlyCardAndPackageVo;
import com.jdd.modules.sdkidentifyresultpushlog.entity.vo.MonthlyEnterTypeVo;
import com.jdd.modules.sdkidentifyresultpushlog.entity.vo.ParkCarEnterLogSyncV3Vo;
import com.jdd.modules.sdkidentifyresultpushlog.mapper.SdkIdentifyResultPushLogMapper;
import com.jdd.modules.sdkidentifyresultpushlog.service.ChargeFeeService;
import com.jdd.modules.sdkidentifyresultpushlog.service.ChargeFeeV2Service;
import com.jdd.modules.sdkidentifyresultpushlog.service.ISdkIdentifyResultPushLogV3Service;
import com.jdd.modules.system.util.MoblieControllerUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @class_name：ISdkIdentifyResultPushLogV3ServiceImpl
 * @comments:第三版月租车进出场逻辑
 * @param:
 * @return:
 * @author: zjf
 * @date: 2022年1月18日
 */
@Service
@Slf4j
public class ISdkIdentifyResultPushLogV3ServiceImpl extends ServiceImpl<SdkIdentifyResultPushLogMapper, SdkIdentifyResultPushLog> implements ISdkIdentifyResultPushLogV3Service {
    @Resource
    private IConfigParkInfoService configParkInfoService;
    @Resource
    private ParkCarEnterLogMapper parkCarEnterLogMapper;
    @Resource
    private ParkCarOutLogMapper parkCarOutLogMapper;
    @Resource
    private ParkWhiteListMapper whiteListMapper;
    @Resource
    private IParkArmInfoService parkArmInfoService;
    @Resource
    private IConfigParkThroughService parkThroughService;
    @Resource
    private IConfigCarTypeService carTypeService;
    @Resource
    private ParkOrderInfoMapper orderInfoMapper;


    @Resource
    private ParkOrderInfoMapper parkOrderInfoMapper;
    @Resource
    private WebSocketServer socketServer;
    @Resource
    private ParkCarStoredMapper parkCarStoredMapper;
    @Resource
    private IparkCarEnterLogService parkCarEnterLogService;
    @Resource
    private IParkSpaceService parkSpaceService;
    @Resource
    private RestTemplateUtils restTemplateUtils;
    @Resource
    private CacheUtil cacheUtil;
    @Resource
    private LedCallInterface ledCallInterface;
    @Resource
    private SdkCallInterface sdkCallInterface;
    @Resource
    private ChargeFeeService chargeFeeService;
    @Resource
    private IParkCarOutAbnormalLogService parkCarOutAbnormalLogService;
    @Resource
    private IParkWorkStationService parkWorkStationService;
    @Resource
    private IParkHandOverLogService parkHandOverLogService;

    public String appScrect;

    public String appId;
    @Resource
    private AsynRestTmplateUtils asynRestTmplateUtils;
    @Autowired
    private IParkCarMonthlyService parkCarMonthlyService;
    @Autowired
    @Lazy
    private IMonthlyCardInfoService monthlyCardInfoService;
    @Autowired
    private IMonthlyCarConfigService monthlyCarConfigService;
    @Autowired
    private IMonthlyCarTypeConvertService monthlyCarTypeConvertService;
    @Autowired
    private IMonthlyPeromissionGroupService monthlyPeromissionGroupService;
    @Resource
    private ChargeFeeV2Service chargeFeeV2Service;
    @Resource
    private IparkCarOutLogService parkCarOutLogService;
    @Resource
    private ParkInnerListMapper parkInnerListMapper;
    @Resource
    private SdkIdentifyResultPushLogMapper sdkIdentifyResultPushLogMapper;
    @Resource
    private SimilarityRatioAlgorithm similarityRatioAlgorithm;

    @Resource
    private IParkCarAbnormalRecordService parkCarAbnormalRecordService;

    @Resource
    private IParkCarOutEntranceRecordService parkCarOutEntranceRecordService;
    /**
     * 新版计费规则 判断出入场
     *
     * @param identifyResultPushLog 车辆识别信息
     * @param parkArmInfo           出入口配置信息
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 11:32:17
     * @version 1.0
     */
    @Override
    public void identifyJudge(SdkIdentifyResultPushLog identifyResultPushLog, ParkArmInfo parkArmInfo) throws Exception {
        log.info("车辆识别，车场名称：{}，车牌号：{}", parkArmInfo.getParkName(), identifyResultPushLog.getLicense());
        // 新版计费规则的出入场逻辑
        if (StringUtils.isNotBlank(parkArmInfo.getTargetAreaId())) {
            log.info("车道目标区域存在，车辆入场，目标区域id：{}", parkArmInfo.getTargetAreaId());
            //出口缓存
            cacheUtil.put(CacheConstant.CURRENT_EXIT_LICENSE_PLATE, identifyResultPushLog.getSerialNo(), identifyResultPushLog.getLicense() + "#" + identifyResultPushLog.getOssImg() + "#" + identifyResultPushLog.getImg() + "#" + parkArmInfo.getArmName());
            // 如果目标区域Id不为空，则是入场
            carEntrance(parkArmInfo, identifyResultPushLog);
            return;
        }
        // 如果目标区id是空，且源区域id不是空，则是出场
        if (StringUtils.isNotBlank(parkArmInfo.getSourceAreaId())) {
        	// 查询所有出口车道信息
        	LambdaQueryWrapper<ParkArmInfo> queryWrapper = Wrappers.lambdaQuery();
        	queryWrapper.eq(ParkArmInfo::getArmStatus, JddConstant.StringNumber.STRING_ONE);
        	queryWrapper.eq(ParkArmInfo::getArmType, JddConstant.StringNumber.STRING_ONE);
        	List<ParkArmInfo> armInfoList = parkArmInfoService.list(queryWrapper);
        	if(CollectionUtils.isNotEmpty(armInfoList)){
        		for(ParkArmInfo armInfo : armInfoList){
        			if(StringUtils.isNotBlank(armInfo.getSerialNo())){
        				// 车辆出场先清除上次缓存的同一辆车的信息
        				String cacheInfo = cacheUtil.get(CacheConstant.CURRENT_EXIT_LICENSE_PLATE, armInfo.getSerialNo());
        				log.info("车道信息为->{},获取的缓存信息为->{}",JSON.toJSONString(armInfo),cacheInfo);
        				if(StringUtils.isNotBlank(cacheInfo)){
        					String[] carInfoArray = cacheInfo.split("#");
                            if (identifyResultPushLog.getLicense().equals(carInfoArray[0])){
                            	cacheUtil.delete(CacheConstant.CURRENT_EXIT_LICENSE_PLATE, armInfo.getSerialNo());
                            }
        				}
        			}
        		}
        	}
        	//缓存当前出口车牌
            cacheUtil.put(CacheConstant.CURRENT_EXIT_LICENSE_PLATE, identifyResultPushLog.getSerialNo(), identifyResultPushLog.getLicense() + "#" + identifyResultPushLog.getOssImg() + "#" + identifyResultPushLog.getImg() + "#" + parkArmInfo.getArmName());
            log.info("车道源区域存在，目标区域为空，车辆出场，源区域id：{}", parkArmInfo.getSourceAreaId());
            outHandle(parkArmInfo, identifyResultPushLog);
            return;
        }
        throw new JddBootException("车道配置错误");
    }

    /**
     * 车牌识别软触发
     *
     * @param serialNo
     * @return com.jdd.common.api.vo.Result
     * @author jdd孙庆伟
     * @date 2021/9/6 15:15:25
     * @version 1.0
     */
    @Override
    public Result triggerExt(String serialNo) {
        return sdkCallInterface.TriggerExtAll(serialNo);
    }

    /**
     * 确认开闸 拒绝 语音播报
     *
     * @param reChargeVo
     * @return com.jdd.common.api.vo.Result
     * @author jdd孙庆伟
     * @date 2021/9/1 09:33:36
     * @version 1.0
     */
    @Override
    public void refuseCarEnter(ReChargeVo reChargeVo) {
        ledCallInterface.RefuseCarEnter(reChargeVo.getSerialNo(), reChargeVo.getPlateNo());
    }

    /**
     * 确认开闸 允许 语音播报
     *
     * @param reChargeVo
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/1 11:03:402
     * @version 1.0
     */
    @Override
    public void openedSaveParkCarEnterLog(ReChargeVo reChargeVo, ParkCarEnterLog parkCarEnterLog) {
        //led播报
        ledCallInterface.TemporaryEntranceAll(reChargeVo.getSerialNo(), reChargeVo.getPlateNo());
        asynRestTmplateUtils.saveParkCarEnterLog(parkCarEnterLog);
    }

    /**
     * 功能描述: 补录用，通过序列号和车牌号查到识别记录，把识别记录的数据返回
     *
     * @param serialNo
     * @param plateNo
     * @return: 识别记录
     * @Author: lcy
     * @Date: 2021/9/16
     */
    private SdkIdentifyResultPushLog findBySerialNoAndPlate(String serialNo, String plateNo) {
        QueryWrapper<SdkIdentifyResultPushLog> wrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(serialNo)) {
            wrapper.eq("serial_no", serialNo);
        }
        wrapper.eq("license", plateNo);
        wrapper.orderByDesc("create_time");
        wrapper.last("limit 1");
        List<SdkIdentifyResultPushLog> list = this.list(wrapper);
        return CollectionUtil.isEmpty(list) ? null : list.get(0);
    }

    /**
     * 补录和匹配的重新计费，返回的订单信息的序列号和图片是出口的
     *
     * @param reChargeVo 前端传的数据，序列号和图片是出口
     * @param reChargeVo 前端传的数据，序列号和图片是出口
     * @return 返回匹配和补录后的订单信息
     * @return 返回匹配和补录后的订单信息
     * @author lcy
     * @date 2021/8/28
     */
    @Override
    public Result<ParkGetCarFeeLogV3DTO> getParkOrderInfoVOResult(ReChargeVo reChargeVo) {
        //总费用
        //这里的车牌号是入场的，序列号和图片是出场的
        Result<?> chargeFeeRe = chargeFeeV2Service.outParkChargeFeeV2(reChargeVo.getParkCode(), reChargeVo.getPlateNo(), reChargeVo.getSerialNo(), reChargeVo.getOssImgPath());
        log.info(String.valueOf(chargeFeeRe));
        //订单上传到云端
        try {
            ParkOrderInfo parkOrderInfos = new ParkOrderInfo();
            BeanUtils.copyProperties(chargeFeeRe.getResult(), parkOrderInfos);
            //订单信息的车牌改成出场的
            if (null != reChargeVo.getOutPlateNo()) {
                parkOrderInfos.setPlateNo(reChargeVo.getOutPlateNo());
            }
            log.info("计费信息同步云端:-{}", parkOrderInfos);
            String url = "";
            restTemplateUtils.post(url, parkOrderInfos, ResultVo.class);
        } catch (Exception e) {
            log.error("计费信息上传到云端上传失败", e);
        }
        return (Result<ParkGetCarFeeLogV3DTO>) chargeFeeRe;
    }

    /**
     * @author lcy
     * @date 2021/9/3
     * @Description 补录时生成的异常入场纪录
     */
    @Override
    public ParkCarEnterLog createAbnormalParkCarEnterLog(ParkArmInfo parkArmInfo, ReChargeVo reChargeVo) {
        //从识别数据库查到出场信息
        SdkIdentifyResultPushLog sdkIdentifyResultPushLog = this.findBySerialNoAndPlate(reChargeVo.getSerialNo(), reChargeVo.getPlateNo());
        //生成入场记录
        ParkCarEnterLog carEnterLog = new ParkCarEnterLog();
        //与正常出场做个标识，查询所有正常出场用
        carEnterLog.setRemark("abnormal");
        carEnterLog.setId(SnowFlakeUtil.getId());
        carEnterLog.setPlateNo(sdkIdentifyResultPushLog.getLicense());
        carEnterLog.setParkCode(reChargeVo.getParkCode());
        carEnterLog.setParkName(parkArmInfo.getParkName());
        carEnterLog.setSerialNo(reChargeVo.getSerialNo());
        carEnterLog.setArmName(parkArmInfo.getArmName());
        //前端传来的区域id，用户自行选择的区域id
        if (null != reChargeVo.getAreaId()) {
            carEnterLog.setAreaId(reChargeVo.getAreaId());
        }
        //批次号
        carEnterLog.setBatchNumber(carEnterLog.getId());
        //补录的车辆类型统一为临时车
        carEnterLog.setCarNature(JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
        carEnterLog.setCarEnterTime(reChargeVo.getEnterDate());
        carEnterLog.setImgPath(sdkIdentifyResultPushLog.getImg());
        if (null != sdkIdentifyResultPushLog.getOssImg()) {
            carEnterLog.setOssImgPath(sdkIdentifyResultPushLog.getOssImg());
        }
        carEnterLog.setIsOut(JddConstant.IsOutType.ISOUT_ONE);
        carEnterLog.setIpAddr(sdkIdentifyResultPushLog.getIpAddr());
        carEnterLog.setCarTypeNo(String.valueOf(sdkIdentifyResultPushLog.getNType()));

        //内部车判断
        ParkInnerInfo isInner = parkInnerListMapper.getIsInner(reChargeVo.getParkCode(), sdkIdentifyResultPushLog.getLicense());
        if (isInner != null) {
            carEnterLog.setCarTypeNo(String.valueOf(JddConstant.TemporaryCarType.TEMPORARY_CAR_INNER));
        }

        carEnterLog.setCarType(null != sdkIdentifyResultPushLog.getLicense() ? MoblieControllerUtils.HAVEPLATE : MoblieControllerUtils.NONEPLATE);
        carEnterLog.setCreateTime(new Date());
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtil.isNotEmpty(workUser)) {
            carEnterLog.setWorkName(parkArmInfo.getWorkStationName());
            carEnterLog.setSysUserName(workUser);
        }
        //保存数据库
        parkCarEnterLogMapper.insert(carEnterLog);
        asynRestTmplateUtils.saveParkCarEnterLog(carEnterLog);
        return carEnterLog;
    }


    /**
     * 车辆入场逻辑
     *
     * @param parkArmInfo           停车场出入口配置信息
     * @param identifyResultPushLog 车辆识别信息
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/3 11:14:29
     * @version 1.0
     */
    private boolean carEntrance(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog identifyResultPushLog) throws Exception {
        //序列号
        String serialNo = parkArmInfo.getSerialNo();
        //车牌号
        String plateNo = identifyResultPushLog.getLicense();
        //车辆类型
        String type = identifyResultPushLog.getNType().toString();
        //白名单播报信息
        String str = "";
        cacheUtil.add("ledSerialNo", serialNo);
        /*
         * 判断是否是无牌车
         */
        if (JddConstant.MqttRecResultPushLog.TYPE_ZERO.equals(type) || JddConstant.MqttRecResultPushLog.THREE_ONE.equals(type)) {
            log.info("无牌车，扫描二维码");
            //无牌车传至云端缓存
          /*  //代码保留
            parkCarEnterLogService.sendUnlicensedCarEnter(parkCode,serialNo,identifyResultPushLog.getOssImg());*/
            parkCarEnterLogService.sendUnlicensedCarEnter(parkArmInfo.getParkCode(), serialNo);
            //提醒扫码
            ledCallInterface.EntranceUnlicensedAll(serialNo);
            //用户扫码后走临时车入场逻辑
            return false;
        }
        /*
         * 军警车默认自动开闸
         */
        if (freeIf(type)) {
            log.info("军警车:{}", plateNo + "免费通行");
            //保存军警车入场记录并播报开闸
            return welcomeDeal(identifyResultPushLog, parkArmInfo, serialNo, plateNo, JddConstant.MqttRecResultPushLog.IS_WHITE_THREE);
        }
        /*
         * 白名单黑名单入场处理
         */
        ParkWhiteList parkWhiteList = whiteListMapper.findByPlateNo(plateNo, JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        if (null != parkWhiteList) {
            log.info("白名单车辆:{}", plateNo + "进行黑白名单判定");
            //如果是白名单
            if (String.valueOf(parkWhiteList.getLicenseType()).equals(JddConstant.ParkWhiteListConstant.LICENSE_TYPE_ONE)) {
                //白名单的true代表可以走
                if (judgeWhite(parkWhiteList)) {
                    ConfigCarType carTypeVo = this.getCarTypeVo(identifyResultPushLog.getNType());
                    if (ObjectUtil.isNotEmpty(carTypeVo)) {
                        identifyResultPushLog.setCarTypeId(carTypeVo.getId());
                        identifyResultPushLog.setCarTypeName("白名单");
                    }
                    //白名单并未过期
                    return welcomeWhiteDeal(identifyResultPushLog, parkArmInfo, serialNo, plateNo, JddConstant.MqttRecResultPushLog.IS_WHITE_FOUR);
                } else {
                    //白名单过期,按照临时车计费
                    cacheUtil.put("parkToEnd", plateNo, JddConstant.OverdueType.WHITE_OVERDUE);
                    log.info("白名单车辆:{}", plateNo + "已过期将按照临时车判断");
                    str = "白名单已过期，将按临时车收费";
                }
            } else {
                //黑名单的true代表就是黑名单，不可以走
                if (judgeWhite(parkWhiteList)) {
                    //如果是黑名单并且iFBroadcast为false 则限制入场
                    ledCallInterface.EntranceBlacklistAll(serialNo, plateNo);
                    log.info("黑名单车辆:{}", plateNo + "禁行");
                    return false;
                }
            }
        }
        /*
         * 月租车入场处理
         */
        List<ParkCarEnterLog> parkCarEnterLogList = parkCarEnterLogService.getIsNotOutCarEnterLog(
                parkArmInfo.getParkCode(), plateNo, JddConstant.ParkCarEnterLog.CAR_NO_OUT);
    	/*ParkCarEnterLog latestParkCarEnterLog = parkCarEnterLogService.getLastCarEnterLog(
        		parkArmInfo.getParkCode(), identifyResultPushLog.getLicense(),
        		JddConstant.ParkCarEnterLog.CAR_NO_OUT);*/
        // 车场内最近一次入场记录
        ParkCarEnterLog latestParkCarEnterLog = new ParkCarEnterLog();
        // 车场区域内最近一次入场记录
        ParkCarEnterLog lastAreaParkCarEnterLog = new ParkCarEnterLog();
        log.info("车场最近一次入场记录->{}", JSON.toJSONString(latestParkCarEnterLog));
        log.info("车场区域内最近一次入场记录->{}", JSON.toJSONString(lastAreaParkCarEnterLog));

        if (CollectionUtils.isNotEmpty(parkCarEnterLogList)) {
            latestParkCarEnterLog = parkCarEnterLogList.get(0);
            for (ParkCarEnterLog entity : parkCarEnterLogList) {
                // 判断车辆入场区域是否有未出场记录
                if (parkArmInfo.getTargetAreaId().equals(entity.getAreaId())
                        && plateNo.equals(entity.getPlateNo())
                        && StringUtils.isNotEmpty(entity.getParkNumber())) {
                    lastAreaParkCarEnterLog = entity;
                    break;
                }
            }
        }

        // 月租车入场判断
        MonthlyEnterTypeVo vo = this.monthlyEntry(
                parkArmInfo, identifyResultPushLog, latestParkCarEnterLog, lastAreaParkCarEnterLog);
        String enterType = (vo.getType() == null ? "" : vo.getType()).toString();
        // 月租车
        if (String.valueOf(MonthlyEnterTypeKind.MONTHLY_CAR.value()).equals(enterType)) {
            return true;
        }
        // 禁止入场
        if (String.valueOf(MonthlyEnterTypeKind.PROHIBIT.value()).equals(enterType)) {
            ledCallInterface.BanMonthOverAll(serialNo, plateNo, vo.getEndTime(), vo.getBroadcastInfo());
            return true;
        }
        // 临时车处理
        if (String.valueOf(MonthlyEnterTypeKind.FULL_BIT.value()).equals(enterType)) {
            identifyResultPushLog.setCarTypeId(vo.getCarType());
        }
        /*
         * 储值车入场处理
         */
        ParkCarStored parkCarStored = parkCarStoredMapper.getEntranceStored(plateNo, JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        if (null != parkCarStored) {
            log.info("储值车:{}", plateNo + "进入禁行判定");
            Boolean ifData = ifPassage(serialNo, parkCarStored.getCarTypeId(), plateNo);
            if (!ifData) {
                log.info("判定禁止通行，禁行时段禁止入场");
                return false;
            }
            log.info("判定禁止通行，进行储值车入场逻辑");
            //进入储值车入场处理
            identifyResultPushLog.setCarTypeId(parkCarStored.getCarTypeId());
            ConfigCarType configCarType = carTypeService.getById(parkCarStored.getCarTypeId());
            identifyResultPushLog.setCarTypeName(configCarType.getCarTypeName());
            return carStored(parkArmInfo, identifyResultPushLog);
        }

        //临时车入场
        ParkInnerInfo isInner = parkInnerListMapper.getIsInner(parkArmInfo.getParkCode(), plateNo);
        // 内部车判断
        if (isInner != null) {
            identifyResultPushLog.setNType(JddConstant.TemporaryCarType.TEMPORARY_CAR_INNER);
            String carTypeId = getCarType(identifyResultPushLog.getNType());
            if (ifPassage(serialNo, carTypeId, plateNo)) {
                log.info("内部车入场，自动开闸");
                ConfigCarType configCarType = this.getCarTypeVo(identifyResultPushLog.getNType());
                if (ObjectUtil.isNotEmpty(configCarType)) {
                    identifyResultPushLog.setCarTypeId(configCarType.getId());
                    identifyResultPushLog.setCarTypeName(configCarType.getCarTypeName());
                }
                ledCallInterface.TemporaryEntranceInConfigAll(serialNo, plateNo);
                saveParkCarEnterLog(identifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
                return true;
            }
        }

        if (StringUtils.isBlank(identifyResultPushLog.getCarTypeId())) {
            ConfigCarType configCarType = this.getCarTypeVo(identifyResultPushLog.getNType());
            if (ObjectUtil.isNotEmpty(configCarType)) {
                identifyResultPushLog.setCarTypeId(configCarType.getId());
                identifyResultPushLog.setCarTypeName(configCarType.getCarTypeName());
            }
        }
        /*
         * 临时车入场处理
         */
        // 根据车辆编号、车牌类型匹配停车场的车辆类型Id
        String carTypeId = getCarType(identifyResultPushLog.getNType());
        //如果禁行配置存在
        log.info("临时车:{}", plateNo + "进入禁行判定");
        Boolean ifData = ifPassage(serialNo, carTypeId, plateNo);
        //临时车在禁行时段
        if (ifData) {
            log.info("判断成功，车辆允许入场,进行临时车入场逻辑");
            //进入临时车处理逻辑
            return temporaryCar(parkArmInfo, identifyResultPushLog, vo, str);
        }
        log.info("判断失败，临时车禁止入场");
        return false;
    }

    /**
     * 获取识别类型对应的车辆类型数据
     */
    private ConfigCarType getCarTypeVo(int type) {
        List<ConfigCarType> list = carTypeService.getCarTypeList();
        for (ConfigCarType carType : list) {
            List<String> typeNo = Arrays.asList(carType.getCarTypeNo().split(","));
            if (typeNo.contains(type + "")) {
                return carType;
            }
        }
        return null;
    }

    /**
     * @param parkArmInfo
     * @param identifyResultPushLog
     * @return
     * @throws Exception
     * @Title: monthlyEntry
     * @Description:第三版月租车逻辑处理
     * @author: zjf
     * @date 2021年11月13日
     * @version 1.0
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public MonthlyEnterTypeVo monthlyEntry(ParkArmInfo parkArmInfo,
                                           SdkIdentifyResultPushLog identifyResultPushLog,
                                           ParkCarEnterLog latestParkCarEnterLog, ParkCarEnterLog lastAreaParkCarEnterLog) throws Exception {
        MonthlyEnterTypeVo vo = new MonthlyEnterTypeVo();
        vo.setType(MonthlyEnterTypeKind.MONTHLY_CAR.value());
        // 1、查询入场车辆是否为月租车
        List<ParkCarMonthly> entranceMonthlyInfo = parkCarMonthlyService.getEntranceMonthlyInfo(
                parkArmInfo.getParkCode(), identifyResultPushLog.getLicense(),
                JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        log.info("查询车辆月租车信息->{}", JSON.toJSONString(entranceMonthlyInfo));
        if (CollectionUtils.isEmpty(entranceMonthlyInfo)) {
            vo.setType(MonthlyEnterTypeKind.FULL_BIT.value());
            return vo;
        }
        String monthlyCardId = entranceMonthlyInfo.get(0).getMonthlyCardId();

        MonthlyCardAndPackageVo cardAndPackage = monthlyCardInfoService.getCardInfoAndPackageInfo(monthlyCardId);
        log.info("月卡信息->{}", JSON.toJSONString(cardAndPackage));
        if (cardAndPackage == null) {
            // 用户开通月卡不存在
            vo.setType(MonthlyEnterTypeKind.FULL_BIT.value());
            return vo;
        }
        vo.setEndTime(cardAndPackage.getEndTime());
        // 查询月租车配置信息
        MonthlyCarConfig carConfig = this.getMonthlyCarConfig(parkArmInfo.getParkCode());
        log.info("月租车配置信息->{}", JSON.toJSONString(carConfig));

        if (carConfig == null) {
            vo.setType(MonthlyEnterTypeKind.PROHIBIT.value());
            return vo;
        }

        for(ParkCarMonthly carMonthly : entranceMonthlyInfo){
        	// 如果车辆入场还未到月卡生效时间则需要生成预转卡记录
        	if (StringUtils.isNotBlank(carMonthly.getAreaId())
        			&& carMonthly.getAreaId().equals(parkArmInfo.getTargetAreaId())
        			&& new Date().before(cardAndPackage.getBeginTime())) {
        		vo.setPreTransfer(JddConstant.IntNumber.INT_ONE);
        		vo.setTransferCarTypeId(cardAndPackage.getMonthlyTypeId());
        		vo.setTransferCarTypeName(cardAndPackage.getMonthlyTypeName());
        		vo.setMonthlyCardId(cardAndPackage.getId());
        		vo.setCarEnterTime(DateUtil.dateToString(cardAndPackage.getBeginTime(),
        				DateUtil.FORMAT_DATETIME_YYYY_MM_DD));
        		break;
        	}
        }

        // 查询有效的车位信息
        List<String> parkNumbers = this.getSurplusPlateNoList(
                identifyResultPushLog.getLicense(), parkArmInfo.getTargetAreaId(),
                cardAndPackage.getId());

        if (CollectionUtils.isNotEmpty(parkNumbers)) {
            vo.setParkNumber(parkNumbers.get(0));
        }

        // 2、如果通行权限不为空则按照车场配置的通行权限通行，如果为空则全区域通行
        if (StringUtils.isNotBlank(cardAndPackage.getThroughAuthorityId())) {
            vo = this.parkConfigCurrent(vo, monthlyCardId, parkArmInfo.getParkCode(),
                    cardAndPackage.getThroughAuthorityId(), parkArmInfo.getTargetAreaId(),
                    identifyResultPushLog.getNType(), carConfig);
            if (vo.getType() != MonthlyEnterTypeKind.MONTHLY_CAR.value()) {
                return vo;
            }
        }
        // 3、判断月租是否过期,过期则判断月租套餐过期按什么车辆类型入场，如果不过期则走其他逻辑
        vo = this.monthlyPackageExpired(
                cardAndPackage.getBeginTime(),
                cardAndPackage.getEndTime(),
                identifyResultPushLog, cardAndPackage.getOverdueTypeId(),
                cardAndPackage.getOverdueTypeName(), vo, parkArmInfo.getParkCode());
        if (vo.getType() == MonthlyEnterTypeKind.FULL_BIT.value()) {
            return vo;
        }

        if (this.isRepeatOut(lastAreaParkCarEnterLog, parkArmInfo)) {
            // 判断车场配置月租车进场模式如果是一进一出或者是一进多出则需要确认开闸
            if (InOutTypeKind.ONE_IN_AND_ONE_OUT.value().equals(carConfig.getInOutType()) ||
                    InOutTypeKind.SINGLE_INPUT_MULTIPLE_OUTPUT.value().equals(carConfig.getInOutType())) {
                // 播报确认开闸信息
                ledCallInterface.repeatRecognitionConfirmOpenedAll(identifyResultPushLog.getSerialNo(), identifyResultPushLog.getLicense());
                // 弹出确认框
                return vo;
            }
            if (StringUtils.isNotBlank(lastAreaParkCarEnterLog.getParkNumber())) {
                vo.setParkNumber(lastAreaParkCarEnterLog.getParkNumber());
            }
            log.info("月租车重复入场，从入场记录中重复获取车位号:{}", lastAreaParkCarEnterLog.getParkNumber());
            // 更新重复入场状态
            lastAreaParkCarEnterLog.setEnterType(JddConstant.IntNumber.INT_ONE);
            lastAreaParkCarEnterLog.setIsOut(JddConstant.StringNumber.STRING_ONE);
            lastAreaParkCarEnterLog.setThisArea(JddConstant.IntNumber.INT_ZERO);
            parkCarEnterLogService.updateById(lastAreaParkCarEnterLog);
            //月租车入场记录上传到云端
            ParkCarEnterLogSyncV3Vo entity = new ParkCarEnterLogSyncV3Vo();
            entity.setParkCarEnterLogStr(JSON.toJSONString(lastAreaParkCarEnterLog));
            entity.setBatchNumber(lastAreaParkCarEnterLog.getBatchNumber());
            String url = "";
            asynRestTmplateUtils.monthlyCarV3(entity, url);
        }
        // 4、多位多车限制判断
        vo = this.multiCarEnterLimit(
                parkArmInfo.getParkCode(), identifyResultPushLog.getLicense(),
                cardAndPackage, parkArmInfo.getTargetAreaId(), identifyResultPushLog, vo, parkNumbers);

        // 月租车场内转场并且不是重复入场则需要转卡
        if (latestParkCarEnterLog != null && JddConstant.StringNumber.STRING_ONE.equals(latestParkCarEnterLog.getCarNature())
                && !this.isRepeatOut(latestParkCarEnterLog, parkArmInfo)) {
            if (BooleanTypeKind.YES.value() == carConfig.getStartTransition()) {
                // 需要判断源区域是否需要转卡
                ParkCarEnterLog parkCarEnterLog = this.transferCard(cardAndPackage, latestParkCarEnterLog);
                if (parkCarEnterLog != null) {
                    vo.setTransferCard(true);
                }
            } else {
                // 更新车位状态
                parkSpaceService.updateParkStatusNullV3(latestParkCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);
                //月租车入场记录上传到云端
                this.syncCloudData(latestParkCarEnterLog, JddConstant.StringNumber.STRING_ZERO);
            }
        }
        // 5、月租车入场
        if (MonthlyEnterTypeKind.MONTHLY_CAR.value() == vo.getType()) {

            // 判断是否在禁止通行时段内
            Boolean ifData = ifPassage(identifyResultPushLog.getSerialNo(), cardAndPackage.getMonthlyTypeId(), identifyResultPushLog.getLicense());
            if (!ifData) {
                log.info("判定禁止通行，禁行时段禁止入场");
                vo.setType(MonthlyEnterTypeKind.PROHIBIT.value());
                vo.setBroadcastInfo("车场禁行时段内,车辆禁止入场！");
                return vo;
            }

			this.monthlyEnterSaveInfo(cardAndPackage,identifyResultPushLog, parkArmInfo,
					String.valueOf(CarNatureKind.MONTHLY_CAR.value()),
					vo.getParkNumber(),carConfig,latestParkCarEnterLog);
			vo.setType(MonthlyEnterTypeKind.MONTHLY_CAR.value());
		}

        // 6、满位禁止入场
        if (MonthlyEnterTypeKind.PROHIBIT.value() == vo.getType()) {
            vo.setBroadcastInfo(EnterAndExportTipsConstant.YOUR_PARKING_SPACE_IS_FULL);
        }
        return vo;
    }

    /**
     * 月租车入场保存数据
     */
    public boolean monthlyEnterSaveInfo(MonthlyCardAndPackageVo cardAndPackage,
                                        SdkIdentifyResultPushLog identifyResultPushLog,
                                        ParkArmInfo parkArmInfo, String isWhite,
                                        String parkNumber, MonthlyCarConfig carConfig,
                                        ParkCarEnterLog latestParkCarEnterLog) {
        // 修改当前区域内的预转卡记录
        parkCarEnterLogService.updateIsTransfer(identifyResultPushLog, parkArmInfo.getSourceAreaId());
        // 组装车辆入场记录
        ParkCarEnterLog parkCarEnterLog = this.packageCarEnterLog(
                identifyResultPushLog, parkArmInfo, latestParkCarEnterLog, DateUtil.newDate(), isWhite);
        // 绑定车位
        parkCarEnterLog.setParkNumber(parkNumber);
        parkCarEnterLog.setMonthlyCardId(cardAndPackage.getId());
        parkCarEnterLog.setCarTypeId(cardAndPackage.getMonthlyTypeId());
        parkCarEnterLog.setCarTypeName(cardAndPackage.getMonthlyTypeName());

        log.info("车辆入场记录->{}", JSON.toJSONString(parkCarEnterLog));
        /**
         * 根据源 区域ID目标区域ID判断出入场。
         * 1、源区域ID为空目标区域不为空则为第一次入场。
         * 2、源区域ID不为空目标区域ID不为空则为区域切换。
         * 3、源区域ID不为空目标区域ID为空则为出场。
         */
        if (StringUtils.isBlank(parkArmInfo.getSourceAreaId())) {
            parkCarEnterLog.setBatchNumber(parkCarEnterLog.getId());
        } else {
            if (null == latestParkCarEnterLog && StringUtils.isBlank(parkCarEnterLog.getParkNumber())) {
                // 车辆无入场记录禁止驶入小车场
                ledCallInterface.OutAbnormalBanAll(identifyResultPushLog.getSerialNo(), identifyResultPushLog.getLicense());
                return false;
            }
            if (latestParkCarEnterLog != null && StringUtils.isNotBlank(latestParkCarEnterLog.getBatchNumber())) {
                parkCarEnterLog.setBatchNumber(latestParkCarEnterLog.getBatchNumber());
                // 保存源区域计费类型转换信息
                monthlyCarTypeConvertService.saveMonthlyCarTypeConvert(
                        identifyResultPushLog.getLicense(),
                        identifyResultPushLog.getParkCode(),
                        parkArmInfo.getTargetAreaId(),
                        latestParkCarEnterLog.getBatchNumber(),
                        latestParkCarEnterLog.getId(),
                        DateUtil.string2Date(latestParkCarEnterLog.getCarEnterTime(), DateUtil.FORMAT_DATETIME_YYYY_MM_DD),
                        cardAndPackage.getMonthlyTypeId(),
                        cardAndPackage.getMonthlyTypeName());
                // 将要转卡的记录在当前区域停靠状态改为未停靠
                parkCarEnterLogService.updateCarEnterLogThisArea(parkArmInfo.getSourceAreaId(), latestParkCarEnterLog.getPlateNo());
            } else {
                parkCarEnterLog.setBatchNumber(parkCarEnterLog.getId());
            }
        }
        parkCarEnterLog.setThisArea(JddConstant.IntNumber.INT_ONE);
        parkCarEnterLogService.save(parkCarEnterLog);
        if (StringUtils.isBlank(parkArmInfo.getSourceAreaId())) {
            parkCarOutEntranceRecordService.assemblyCarEntranceRecord(parkCarEnterLog);
        }
        // 将获得的车位的停车状态改为有车辆停靠
        parkSpaceService.updateParkStatusV3(parkCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ONE, parkCarEnterLog.getPlateNo());
        //月租车入场记录上传到云端
        ParkCarEnterLogSyncV3Vo entity = new ParkCarEnterLogSyncV3Vo();
        entity.setParkCarEnterLogStr(JSON.toJSONString(parkCarEnterLog));
        entity.setParkStatus(JddConstant.ParkSpace.PARKSTATUS_ONE);
        entity.setSourceAreaId(parkArmInfo.getSourceAreaId());
        entity.setLastParkCarEnterLogStr(JSON.toJSONString(latestParkCarEnterLog));
        String url = "";
        asynRestTmplateUtils.monthlyCarOut(entity, url);
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        Map<String, Object> data = parkArmInfoService.getData(workUser);
        try {
            socketServer.sendMessageAll(JSONObject.toJSONString(data));
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 月租车播报处理逻辑
        this.monthlyCardBroadcastTipsInfo(carConfig, identifyResultPushLog.getSerialNo(),
                identifyResultPushLog.getLicense(), cardAndPackage.getEndTime());
        return true;
    }

    /**
     * 月租车提示播报信息
     */
    private void monthlyCardBroadcastTipsInfo(MonthlyCarConfig carConfig,
                                              String serialNo, String plateNo, Date endTime) {
        String str = EnterAndExportTipsConstant.MONTHLY_CAR + plateNo +
                EnterAndExportTipsConstant.WELCOME_PRESENCE;
        if (carConfig != null) {
            // 获取剩余天数
            long surplusDay = DateUtil.getDaySub(DateUtil.dateToString(new Date(), DateUtil.FORMAT_DATETIME_YYYY_MM_DD),
                    DateUtil.dateToString(endTime, DateUtil.FORMAT_DATETIME_YYYY_MM_DD));
            Integer temporaryDay = 30;
            long day = surplusDay + 1;
            log.info("将要到期时间->" + day);
            if (StringUtils.isNotBlank(carConfig.getPeriodMonthlyDays())) {
                temporaryDay = Integer.parseInt(carConfig.getPeriodMonthlyDays());
            }
            if (MonthlyEffectiveDaysKind.ALL_HINT.value().equals(carConfig.getMonthlyEffectiveDays())
                    && day > temporaryDay) {
                // 月租车剩余天数全提示
                str = str + EnterAndExportTipsConstant.AVAILABLE_DATE + day +
                        EnterAndExportTipsConstant.DAY;
            } else if (MonthlyEffectiveDaysKind.ALL_HINT.value().equals(carConfig.getMonthlyEffectiveDays())
                    || (MonthlyEffectiveDaysKind.TEMPORARY_HINT.value().equals(carConfig.getMonthlyEffectiveDays())
                    && day <= temporaryDay)) {
                // 月租车剩余天数全显示小于等于临期天数提示
                str = str + EnterAndExportTipsConstant.AVAILABLE_DATE + day +
                        EnterAndExportTipsConstant.DELAY_AS_SOONR_AS_POSSIBLE;
            }
        }
        log.info("月租车播报信息->{}", str.trim());
        //直接抬杆
        try {
            ledCallInterface.MonthlyEntranceAll(serialNo, plateNo, str.trim());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 转卡逻辑
     * 1、如果一个套餐内开通多个区域的月卡。
     * 2、根据区域查询入场车辆是否有月租车，如果有月租车则不处理。
     * 3、如果入场车辆都是临时车则需要把临时车转为月租车，新增转卡记录。
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public ParkCarEnterLog transferCard(MonthlyCardAndPackageVo cardAndPackage, ParkCarEnterLog latestParkCarEnterLog) {
        // 查询需要转卡的入场记录
        ParkCarEnterLog parkCarEnterLog = this.getByBatchNumberEnterLog(latestParkCarEnterLog);
        log.info("查询需要转卡的记录->{}", JSON.toJSONString(parkCarEnterLog));
        // 将要转卡的记录在当前区域停靠状态改为未停靠
        parkCarEnterLogService.updateCarEnterLogThisArea(latestParkCarEnterLog.getAreaId(), latestParkCarEnterLog.getPlateNo());
        if (parkCarEnterLog == null) {
            // 更新车位状态
            parkSpaceService.updateParkStatusNullV3(latestParkCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);

            //月租车入场记录上传到云端
            this.syncCloudData(latestParkCarEnterLog, JddConstant.StringNumber.STRING_ZERO);
            return null;
        }

        parkCarEnterLog.setId(IdWorker.getIdStr());
        parkCarEnterLog.setIsTransfer(IsTransferKind.TOO_TRANSFER.value());
        parkCarEnterLog.setCarEnterTime(DateUtil.fomatDateTimeStr(new Date()));
        parkCarEnterLog.setParkNumber(latestParkCarEnterLog.getParkNumber());
        parkCarEnterLog.setCarNature(String.valueOf(CarNatureKind.MONTHLY_CAR.value()));
        parkCarEnterLog.setCreateTime(new Date());
        parkCarEnterLog.setCarTypeId(latestParkCarEnterLog.getCarTypeId());
        parkCarEnterLog.setCarTypeName(latestParkCarEnterLog.getCarTypeName());
        parkCarEnterLog.setEnterType(JddConstant.IntNumber.INT_ZERO);
        parkCarEnterLog.setThisArea(JddConstant.IntNumber.INT_ONE);
        parkCarEnterLog.setCarType(latestParkCarEnterLog.getCarType());
        parkCarEnterLog.setMonthlyCardId(cardAndPackage.getId());
        parkCarEnterLog.setSynchronousStatus(JddConstant.StatusType.STATUS_Y);
        // 将获得的车位的停车状态改为有车辆停靠
        parkCarEnterLogService.save(parkCarEnterLog);
        parkSpaceService.updateParkStatusV3(parkCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ONE, parkCarEnterLog.getPlateNo());
        //月租车入场记录上传到云端
        ParkCarEnterLogSyncV3Vo entity = new ParkCarEnterLogSyncV3Vo();
        entity.setParkCarEnterLogStr(JSON.toJSONString(parkCarEnterLog));
        entity.setParkStatus(JddConstant.ParkSpace.PARKSTATUS_ONE);
        entity.setSourceAreaId(latestParkCarEnterLog.getAreaId());
        entity.setLastParkCarEnterLogStr(JSON.toJSONString(latestParkCarEnterLog));
        String url = "";
        asynRestTmplateUtils.monthlyCarV3(entity, url);

        return parkCarEnterLog;
    }

    /**
     * 同步状态
     */
    public void syncCloudData(ParkCarEnterLog latestParkCarEnterLog, String thisArea) {
        //月租车入场记录上传到云端
        ParkCarEnterLogSyncV3Vo entity = new ParkCarEnterLogSyncV3Vo();
        entity.setParkCarEnterLogStr(JSON.toJSONString(latestParkCarEnterLog));
        entity.setParkStatus(JddConstant.ParkSpace.PARKSTATUS_ZERO);
        entity.setLastParkCarEnterLogStr(JSON.toJSONString(latestParkCarEnterLog));
        entity.setThisArea(JddConstant.StringNumber.STRING_ZERO);
        String url = "";
        asynRestTmplateUtils.monthlyCarV3(entity, url);
    }

    /**
     * 查询需要转卡的记录
     */
    public ParkCarEnterLog getByBatchNumberEnterLog(ParkCarEnterLog carEnterLog) {
        List<String> plateNoList = parkCarMonthlyService.getTemporaryCarList(
                carEnterLog.getAreaId(), carEnterLog.getPlateNo(),
                carEnterLog.getParkNumber(), carEnterLog.getMonthlyCardId());
        if (CollectionUtils.isEmpty(plateNoList)) {
            return null;
        }
        log.info("转卡的车牌号->{}", JSON.toJSONString(plateNoList));
        List<ParkCarEnterLog> carEnterLogList = parkCarEnterLogService.
                getThisAreaCarEnterList(carEnterLog.getAreaId(), plateNoList);
        log.info("入场未离场的临时车记录->{}", JSON.toJSONString(carEnterLogList));
        // 保存在此区域内月租车拍照
        if (CollectionUtils.isNotEmpty(carEnterLogList)) {
            return carEnterLogList.get(0);
        }
        return null;
    }

    /**
     * @return
     * @Title: getByPlateNoParkCarMonthly
     * @Description:查询有效的月租车位信息
     * @author: zjf
     * @date 2021年11月15日
     * @version 1.0
     */
    private List<String> getByPlateNoParkCarMonthly(String monthlyCardId, String areaId) {
        QueryWrapper<ParkCarMonthly> queryWrapper = Wrappers.query();
        queryWrapper.eq("status", JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        queryWrapper.eq("monthly_card_id", monthlyCardId);
        queryWrapper.eq("area_id", areaId);
        return parkCarMonthlyService.list(queryWrapper).stream().map(ParkCarMonthly::getParkNumber).distinct().collect(Collectors.toList());
    }

    /**
     * @return
     * @Title: isMonthlyPackageInfo
     * @Description:一位多车入场限制
     * @author: zjf
     * @date 2021年11月15日
     * @version 1.0
     */
    private MonthlyEnterTypeVo multiCarEnterLimit(String parkCode, String plateNo,
                                                  MonthlyCardAndPackageVo cardAndPackage, String areaId,
                                                  SdkIdentifyResultPushLog identifyResultPushLog, MonthlyEnterTypeVo vo,
                                                  List<String> surplusPlateNoList) {
        // 查询当前区域内月租车位信息
        List<String> parkNumberList = this.getByPlateNoParkCarMonthly(cardAndPackage.getId(), areaId);
        log.info("月租车位信息->{}", JSON.toJSONString(parkNumberList));
        // 如果当前区域内车位为空则为临时车
        if (CollectionUtils.isEmpty(parkNumberList)) {
            vo.setType(MonthlyEnterTypeKind.FULL_BIT.value());
            return vo;
        }
        // 查询区域内剩余车位
        log.info("剩余车位信息->{}", JSON.toJSONString(surplusPlateNoList));
        if (StringUtils.isNotBlank(vo.getParkNumber())) {
            surplusPlateNoList.add(vo.getParkNumber());
        }
        // 区域内有空闲车位直接分配车位
        if (CollectionUtils.isNotEmpty(surplusPlateNoList)) {
            vo.setParkNumber(surplusPlateNoList.get(0));
            return vo;
        }
        // 区域内没有车位则，判断月卡套餐满位处理方式
        // 判断满位处理方式如果值为空则禁止入场，如果值为0则按车辆性质对应车辆类型入场，否则按车辆类型处理
        if (StringUtils.isBlank(cardAndPackage.getFullManageId()) ||
                JddConstant.StringNumber.STRING_NEGATIVE_ONE.equals(cardAndPackage.getFullManageId())) {
            identifyResultPushLog.setCarTypeId(cardAndPackage.getFullManageId());
            identifyResultPushLog.setCarTypeName(cardAndPackage.getFullManageName());
            vo.setType(MonthlyEnterTypeKind.PROHIBIT.value());
            return vo;
        }

        // 如果值为0则按车辆性质对应车辆类型入场，否则按车辆类型处理
        String carTypeId = cardAndPackage.getFullManageId();
        if (JddConstant.StringNumber.STRING_ZERO.equals(cardAndPackage.getFullManageId())) {
            carTypeId = this.getCarType(identifyResultPushLog.getNType());
        }

        vo.setType(MonthlyEnterTypeKind.FULL_BIT.value());
        vo.setCarType(carTypeId);
        return vo;
    }

    /**
     * 根据车牌号查询入场车辆
     */
    private List<String> getSurplusPlateNoList(
            String plateNo, String areaId, String monthlyCardId) {
        QueryWrapper<ParkSpace> queryWrapper = new QueryWrapper<ParkSpace>();
        queryWrapper.eq("area_id", areaId);
        queryWrapper.eq("status", JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        queryWrapper.eq("monthly_card_id", monthlyCardId);
        List<ParkSpace> parkSpaceList = parkSpaceService.list(queryWrapper);
        List<String> parkNumbers = new ArrayList<String>();
        if (CollectionUtils.isEmpty(parkSpaceList)) {
            return parkNumbers;
        }
        for (ParkSpace parkSpace : parkSpaceList) {
            // 初始化入场车辆车位信息
            if (StringUtils.isNotBlank(plateNo) && plateNo.equals(parkSpace.getPlateNo())) {
                parkNumbers.add(parkSpace.getParkNumber());
                break;
            }
            // 获取空闲车位
            if (JddConstant.StringNumber.STRING_ZERO.equals(parkSpace.getParkStatus())
                    || StringUtils.isBlank(parkSpace.getPlateNo())) {
                parkNumbers.add(parkSpace.getParkNumber());
            }
        }
        return parkNumbers;
    }

    /**
     * 判断是否重复入场
     */
    private boolean isRepeatOut(ParkCarEnterLog lastAreaParkCarEnterLog, ParkArmInfo parkArmInfo) {
        String serialNo = lastAreaParkCarEnterLog.getSerialNo();
        ParkArmInfo parkArmInfos = new ParkArmInfo();
        if (StringUtils.isNotBlank(serialNo)){
              parkArmInfos = parkArmInfoService.findParkArmInfoBySerialNo(serialNo);
        }

        boolean flag = false;
        if (null != lastAreaParkCarEnterLog &&
                StringUtils.isNotBlank(lastAreaParkCarEnterLog.getAreaId())
                && lastAreaParkCarEnterLog.getAreaId().equals(parkArmInfo.getTargetAreaId())
                && StringUtils.isBlank(parkArmInfos.getSourceAreaId()) && StringUtils.isBlank(parkArmInfo.getSourceAreaId()) ){
            flag = true;
        }

        return flag;
    }

    /**
     * 判断月租是否过期,过期则判断月租套餐过期按什么车辆类型入场，如果不过期则走其他逻辑
     */
    private MonthlyEnterTypeVo monthlyPackageExpired(Date beginTime, Date endTime,
                                                     SdkIdentifyResultPushLog identifyResultPushLog,
                                                     String overdueTypeId, String overdueTypeName,
                                                     MonthlyEnterTypeVo vo, String parkCode) {
        if (DateUtil.betweenDateTime(new Date(), beginTime, endTime)) {
            return vo;
        }
        // 判断过期是否按车辆性质入场
        if (JddConstant.StringNumber.STRING_ZERO.equals(overdueTypeId)) {
            // 查询车辆性质对应的车辆类型
            String carTypeId = this.getCarType(identifyResultPushLog.getNType());
            // 如果车辆性质没有对应的车辆类型则查询第一个临时车辆类型
            identifyResultPushLog.setCarTypeId(carTypeId);
        } else {
            // 月卡过期设置过期按临时车类型
            identifyResultPushLog.setCarTypeId(overdueTypeId);
            identifyResultPushLog.setCarTypeName(overdueTypeName);
        }
        // 过期走临时车入场逻辑
        vo.setType(MonthlyEnterTypeKind.FULL_BIT.value());
        return vo;
    }

    /**
     * 通行权限不为空则按照车场配置的通行权限通行
     */
    private MonthlyEnterTypeVo parkConfigCurrent(
            MonthlyEnterTypeVo vo, String monthlyCardId,
            String parkCode, String permissionid, String areaId,
            Integer type, MonthlyCarConfig carConfig) {
        // 查询区域和权限组关联信息
        int count = monthlyPeromissionGroupService.
                getByMonthlyCardIdAndMonthlyPeromissionCount(permissionid, areaId);
        // 如果数据为0则表示区域和权限组未关联给出播报提示
        if (count > 0) {
            return vo;
        }
        // 如果月租车配置不存在或者为-1则禁止入场
        if (carConfig == null ||
                EnterTypeNoPermissionKind.NOT_INTO.value().equals(
                        carConfig.getEnterTypeNoPermission())) {
            vo.setType(MonthlyEnterTypeKind.PROHIBIT.value());
            vo.setBroadcastInfo(EnterAndExportTipsConstant.UNAUTHORIZED);
            return vo;
        }
        // 过期默认车辆类型转卡
        String carTypeId = carConfig.getEnterTypeNoPermission();
        if (EnterTypeNoPermissionKind.CAR_NATURE.value().equals(
                carConfig.getEnterTypeNoPermission())) {
            // 按车辆性质转卡
            carTypeId = this.getCarType(type);
        }
        vo.setType(MonthlyEnterTypeKind.FULL_BIT.value());
        vo.setCarType(carTypeId);
        return vo;
    }

    /**
     * 查询车场的配置信息
     */
    public MonthlyCarConfig getMonthlyCarConfig(String parkCode) {
        QueryWrapper<MonthlyCarConfig> queryWrapper = Wrappers.query();
        queryWrapper.eq("park_code", parkCode);
        queryWrapper.eq("del_flag", 0);
        List<MonthlyCarConfig> carConfigList = monthlyCarConfigService.list(queryWrapper);
        if (CollectionUtils.isNotEmpty(carConfigList)) {
            return carConfigList.get(0);
        }
        return null;
    }

    /*** 功能描述: 判断是否属于有效得白名单/黑名单
     * @Author: lcy
     * @Date: 2021/11/24
     */
    private boolean judgeWhite(ParkWhiteList parkWhiteList) throws ParseException {
        Boolean flag = false;
        //永久则直接返回
        if (ObjectUtil.isNotEmpty(parkWhiteList.getIsForever())) {
            if (JddConstant.StringNumber.STRING_ONE.equals(parkWhiteList.getIsForever())) {
                log.info("--------------该车辆为永久黑/白名单--------------");
                return true;
            }
        }
        //判断非永久时，是否还算白名单
        Calendar now = Calendar.getInstance();
        //一周第一天是否为星期天
        boolean isFirstSunday = (now.getFirstDayOfWeek() == Calendar.SUNDAY);
        //获取周几
        int weekDay = now.get(Calendar.DAY_OF_WEEK);
        //若一周第一天为星期天，则-1
        if (isFirstSunday) {
            weekDay = weekDay - 1;
            if (weekDay == 0) {
                weekDay = 7;
            }
        }
        //周几有值就判断
        if (ObjectUtil.isNotEmpty(parkWhiteList.getWeekDay())) {
            String[] weekDays = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
            //白名单新加了个周几不算白名单，星期不包含也不算白名单
            if (parkWhiteList.getWeekDay().contains(weekDays[weekDay])) {
                log.info("--------------该车辆为黑/白名单，星期包含今天--------------");
                flag = true;
            } else {
                log.info("--------------该车辆为黑/白名单，星期不包含今天--------------");
                return false;
            }
        }

        //日期的判断
        if (ObjectUtil.isNotEmpty(parkWhiteList.getStartDate()) && ObjectUtil.isNotEmpty(parkWhiteList.getEndDate())) {
            //现在时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            String s = sdf.format(new Date());
            Date nowDate = sdf.parse(s);
            //开始与过期时间
            Date startDate = parkWhiteList.getStartDate();
            Date endDate = parkWhiteList.getEndDate();
            //判断日期
            //true代表在时间范围内
            if (isEffectiveDate(nowDate, startDate, endDate)) {
                log.info("--------------该车辆为黑/白名单，日期包含今天--------------");
                flag = true;
            } else {
                log.info("--------------该车辆为黑/白名单，日期不包含今天--------------");
                return false;
            }
        }
        //时间的判断
        if (ObjectUtil.isNotEmpty(parkWhiteList.getStartTime()) && ObjectUtil.isNotEmpty(parkWhiteList.getEndTime())) {
            SimpleDateFormat dayFormat = new SimpleDateFormat("HH:mm:ss");
            //现在时间
            String s = dayFormat.format(new Date());
            Date nowTime = dayFormat.parse(s);
            //开始与过期时间
            Date startTime = parkWhiteList.getStartTime();
            Date endTime = parkWhiteList.getEndTime();
            //判断日期
            //true代表在时间范围内
            if (isEffectiveDate(nowTime, startTime, endTime)) {
                log.info("--------------该车辆为黑/白名单，时间包含今天--------------");
                flag = true;
            } else {
                log.info("--------------该车辆为黑/白名单，时间不包含今天--------------");
                return false;
            }
        }
        return flag;
    }

    /**
     * 车场出场逻辑
     *
     * @param identifyResultPushLog 车辆识别信息
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 11:14:36
     * @version 1.0
     */
    private void outHandle(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog identifyResultPushLog) throws Exception {
        Map<String, Object> data = new HashMap<>(16);
        //序列号
        String serialNo = parkArmInfo.getSerialNo();
        //车牌号
        String plateNo = identifyResultPushLog.getLicense();
        //车辆类型
        String type = identifyResultPushLog.getNType().toString();
        //车场名称
        String parkName = parkArmInfo.getParkName();
        //车场编号
        String parkCode = parkArmInfo.getParkCode();

        /*
         * 无牌车出场处理
         */
        if (JddConstant.MqttRecResultPushLog.TYPE_ZERO.equals(type) || JddConstant.MqttRecResultPushLog.THREE_ONE.equals(type)) {
            parkCarOutLogService.sendUnlicensedCarOut(parkCode, serialNo);
            //场端调用云端删除前一辆车redis缓存
            asynRestTmplateUtils.redisDelete(serialNo);
            ledCallInterface.OutUnlicensedAll(serialNo);
            return;
        }
        /*
         * 军警车免费通行
         */
        if (freeIf(type)) {
            log.info("军警车:{}", plateNo + "免费通行");
            ledCallInterface.OutSpecialAll(serialNo, plateNo);
            //保存出场纪录
            handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_THREE);
            return;
        }
        /*
         * 白名单出场处理
         */
        ParkWhiteList isWhite = whiteListMapper.findByPlateNo(plateNo, JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        if (null != isWhite) {
            log.info("白名单:{}", plateNo + "出场处理");
            if (JddConstant.ParkWhiteListConstant.LICENSE_TYPE_ONE.equals(String.valueOf(isWhite.getLicenseType()))) {
                //判断白名单有效期
                if (judgeWhite(isWhite)) {
                    log.info("白名单:{}", plateNo + "出场");
                    ledCallInterface.OutParkWhiteAll(serialNo, plateNo);
                    //保存出场纪录
                    handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_FOUR);
                    return;
                }
            }
        }

        // 根据车场编号、车牌号更新预转卡记录
        boolean result = parkCarEnterLogService.updateIsTransfer(identifyResultPushLog, parkArmInfo.getSourceAreaId());
        log.info("根据车场编号、车牌号更新预转卡记录->{}", identifyResultPushLog);
        if (result) {
            //场端预转卡记录上传云端
            ParkCarEnterLogSyncV3Vo entity = new ParkCarEnterLogSyncV3Vo();
            ParkCarEnterLog parkCarEnterLog = new ParkCarEnterLog();
            parkCarEnterLog.setParkCode(parkCode);
            parkCarEnterLog.setPlateNo(plateNo);
            entity.setParkCarEnterLogStr(JSON.toJSONString(parkCarEnterLog));
            entity.setParkStatus(JddConstant.ParkSpace.PARKSTATUS_ONE);
            entity.setParkStatus(parkArmInfo.getSourceAreaId());
            String url = "";
            asynRestTmplateUtils.monthlyCarV3(entity, url);
        }

        Result<?> resultRe = null;
        //收费车辆处理
        try {
            log.info("出场识别，请求算费服务，车场编号：{}，车牌号：{}，设备序列号：{}", parkCode, plateNo, serialNo);
            resultRe = chargeFeeV2Service.outParkChargeFeeV2(parkCode, plateNo, serialNo, identifyResultPushLog.getImg());
        } catch (Exception e) {
            log.error("请求算费服务失败");
            ledCallInterface.OutAbnormalAll(serialNo, plateNo);
        }
        log.info("调用计费规则返回结果：{}", JSONObject.toJSONString(resultRe));

        // 根据车场编号和车牌号查询是否为车场月租车
        List<ParkCarMonthly> parkCarMonthlyList = parkCarMonthlyService.getEntranceMonthlyInfo(
                identifyResultPushLog.getParkCode(), identifyResultPushLog.getLicense(),
                JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);

        // 无入场记录处理逻辑
        if (JddConstant.NO_RECORD_ERROR == resultRe.getCode()) {
            log.info("车牌:{}", plateNo + "无入场记录不抬杆");
            String license = cacheUtil.get(CacheConstant.PARK_CAR_OUT, serialNo);
            log.info("获取上一辆出场车牌:{}", license);
            if (StringUtils.isNotEmpty(license)) {
                //判断是否是因为车辆出口多次识别导致车辆无入场记录
                log.info("获取出口车道上一辆已出场车辆:{},当前车道车牌{}", license, plateNo);
                if (plateNo.equals(license)) {
                    //一直直接放行
                    ledCallInterface.FieldFeeAllOut(serialNo, plateNo);
                    return;
                }
            }
            boolean flag = this.noEnterInfo(identifyResultPushLog, parkArmInfo, parkCarMonthlyList);
            if (!flag) {
                //无入场记录是否能匹配到
                ledCallInterface.OutAbnormalBanAll(serialNo, plateNo);
                if (noMatchingEnterLog(plateNo, parkCode)) {
                    log.info(JddConstant.AdmissionRecordsCarType.NO_OPEN_MESSAGE);
                }
                data = generateParkOrderInfoVO(parkArmInfo, identifyResultPushLog, type, plateNo, 0L);
                socketServer.sendInfo(JSONObject.toJSONString(data), parkArmInfo.getUsedUserId());
            }
            //无入场纪录异常记录保存
            parkCarAbnormalRecordService.saveAbnormalRecord(identifyResultPushLog,parkArmInfo, AbnormalTypeEnums.NO_ENTRY_RECORD);
            return;
        }

        //调用计费规则异常处理
        if (JddConstant.SUCCESS_CODE != resultRe.getCode() && JddConstant.NO_RECORD_ERROR != resultRe.getCode()) {
            ledCallInterface.OutAbnormalAll(serialNo, plateNo);
            return;
        }
        if (null == resultRe.getResult()) {
            return;
        }
        //收费车辆处理订单详情
        ParkGetCarFeeLogV3DTO parkGetCarFeeLog = JSONObject.parseObject(JSONObject.toJSONString(resultRe.getResult()), ParkGetCarFeeLogV3DTO.class);
        //收费车辆处理订单详情
        ParkOrderInfoVO parkOrderInfo = parkGetCarFeeLog.getParkOrderInfoVO();
        //停车费已支付处理
        if (JddConstant.StringNumber.STRING_ZERO.equals(parkOrderInfo.getChargeUnpaid())) {
            this.monthlyPayTips(parkOrderInfo.getChargeTotalFee(), serialNo, plateNo, parkCarMonthlyList,parkOrderInfo.getChargeDuration());
            // 月租车一出无如场记录确认开闸
            handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
            return;
        }

        /*
         * 优惠卷处理
         */
        ParkCarStored parkCarStored = parkCarStoredMapper.getEntranceStored(plateNo, "Y");
        ConfigParkInfo configParkInfo = configParkInfoService.findConfigParkInfoByParkCode();
        if (configParkInfo.getUseCoupon().equals(IsOpenKind.YES.value())){
            if (null == parkCarStored) {
                log.info("车牌:{}", plateNo + "查询可用优惠卷");
                //组装参数
                MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
                request.add("parkCode", parkCode);
                request.add("plateNo", plateNo);
                //停车时常
                request.add("elapsedTime", String.valueOf(parkOrderInfo.getChargeDuration()));
                HttpHeaders headers = new HttpHeaders();
                // 以表单的方式提交
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                HttpEntity<MultiValueMap<String, String>> requests = new HttpEntity<>(request, headers);
                String url = "";

                ResponseEntity<ResultVo> posts = restTemplateUtils.post(url, requests, ResultVo.class);
                ResultVo body = posts.getBody();

                if (null != body && body.getCode() == 200 && (StringUtils.isNotBlank(String.valueOf(body.getData())) && !body.getData().equals("null"))) {
                    String discountCouponDetailStr = (String) body.getData();
                    //查询的优惠卷免费时常大于停车时常
                    DiscountCouponDetail discountCouponDetail = JSONObject.parseObject(discountCouponDetailStr, DiscountCouponDetail.class);
                    log.info("车牌:{},可用优惠卷:{}", plateNo, discountCouponDetail.getCouponName());
                    ledCallInterface.OutDiscountAll(serialNo, plateNo, discountCouponDetail);
                    identifyResultPushLog.setFreeAllCoupon(true);
                    //保存出场记录
                    handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
                    //将十分钟内的异常记录删除
                    parkCarAbnormalRecordService.deletedLastestAbnormalRecord(parkCode,plateNo,serialNo);
                    //保存使用免费时长券/全免券/小时券的订单信息
                    saveParkOrderInfo(discountCouponDetail, parkOrderInfo, parkArmInfo);
                    return;
                }
            }
        }
        /*
         * 储值车出场处理
         */
        if (null != parkCarStored) {
            log.info("储值车:{}", plateNo + "出场处理");
            //余额
            Integer payFee = parkCarStored.getPayFee();
            //本次金额
            Integer unpayFee = Integer.parseInt(parkOrderInfo.getChargeUnpaid()) / 100;
            if (payFee >= unpayFee) {
                log.info("储值车:{},余额大于本次费用-----余额:{},本次费用:{}", plateNo, payFee, unpayFee);
                //支付费用-本次金额=剩余金额
                int remaining = payFee - unpayFee;
                log.info("储值车出场:{},支付后余额为:{}", plateNo, remaining);
                ledCallInterface.OutStoredPayFeeAll(serialNo, plateNo, unpayFee, remaining, parkOrderInfo);

                //保存订单
                ParkOrderInfo orderInfo = new ParkOrderInfo();
                orderInfo.setCarEnterId(parkOrderInfo.getCarEnterId());
                orderInfo.setEnterTime(parkOrderInfo.getEnterTime());
                orderInfo.setChargeTotalFee(parkOrderInfo.getChargeUnpaid());
                orderInfo.setOutTime(DateUtils.formatDateTime());
                orderInfo.setId(SnowFlakeUtil.getId());
                orderInfo.setCreateTime(new Date());
                orderInfo.setParkCode(parkCode);
                orderInfo.setParkName(parkName);
                orderInfo.setSerialNo(serialNo);
                orderInfo.setPlateNo(plateNo);
                orderInfo.setPayTime(DateUtils.formatDateTime());
                orderInfo.setChargeDuration(parkOrderInfo.getChargeDuration());
                orderInfo.setCarType("1");
                orderInfo.setChargeUnpaid(parkOrderInfo.getChargeUnpaid());
                orderInfo.setChargeTotalFee(orderInfo.getChargeTotalFee());
                orderInfo.setChargePaid(String.valueOf(parkOrderInfo.getChargeUnpaid()));
                orderInfo.setOrderStatus("2");
                orderInfo.setPayType("5");
                orderInfo.setArmName(parkArmInfo.getArmName());
                orderInfo.setChargeDue(orderInfo.getChargeTotalFee());
                try {
                    //加上工作站名和岗亭人账户
                    String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
                    if (ObjectUtil.isNotEmpty(workUser)) {
                        orderInfo.setWorkName(parkArmInfo.getWorkStationName());
                        orderInfo.setSysUserName(workUser);
                        //交接班更新,储值车这里只更新钱数
                        //赋值应收钱数，方便统计
                        parkHandOverLogService.updateHandOverAuto(null, orderInfo, workUser, 0);
                    }
                } catch (Exception e) {
                    log.error("-----------------车辆出场交接班更新或者发送场内车辆详情失败----------------");
                }
                orderInfoMapper.insert(orderInfo);
                //更新储值车余额
                parkCarStored.setPayFee(remaining);
                parkCarStoredMapper.updateById(parkCarStored);
                //交接班更新，储值车这里只更新出场数
                handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_TWO);
                return;
            }
        }
        //webSocket发送订单信息
        data.put("parkOrderInfo", parkOrderInfo);
        socketServer.sendInfo(JSONObject.toJSONString(data), parkArmInfo.getUsedUserId());
        if (isWhite != null){
            String str = "白名单" + plateNo + "已过期,收费" + Integer.parseInt(parkOrderInfo.getChargeUnpaid()) / 100 + "元";
            ledCallInterface.customizedConfirmOpenedAll(serialNo, plateNo, str.trim());
        }
        else if (CollectionUtils.isNotEmpty(parkCarMonthlyList) &&
                new Date().after(parkCarMonthlyList.get(0).getEndTime())) {
            String str = "月租车" + plateNo + "已过期,收费" + Integer.parseInt(parkOrderInfo.getChargeUnpaid()) / 100 + "元";
            ledCallInterface.customizedConfirmOpenedAll(serialNo, plateNo, str.trim());
            log.info("月租车过期出场播报信息->{}", str);
        } else {
            //临时车出场、或储值车余额不足出场
            log.info("临时车:{},金额:{}", plateNo, Integer.parseInt(parkOrderInfo.getChargeUnpaid()) / 100);
            ledCallInterface.OutShowFeeAll(serialNo, plateNo, parkOrderInfo);
        }

        //计费信息同步发送云端
        ParkOrderInfo parkOrderInfos = new ParkOrderInfo();
        BeanUtils.copyProperties(parkOrderInfo, parkOrderInfos);
        asynRestTmplateUtils.synchronousParkOrderFee(parkOrderInfos);
    }

    /**
     * 无入场记录是否能匹配到
     *
     * @param plateNo  车牌号
     * @param parkCode 车场编码
     * @return true
     */
    private boolean noMatchingEnterLog(String plateNo, String parkCode) {
        //最小保留匹配度
        float minSimilarityRatio = 50.0f;

        //创建集合用于模糊查询条件
        List<String> plateStrList = new ArrayList<>();

        //将当前车牌字符串拆分成数组，并按顺序将相邻的三位数组元素组成字符串添加到集合中
        char[] plateNoChars = plateNo.toUpperCase().toCharArray();
        //合并添加到集合中
        for (int i = 0; i < plateNoChars.length - 2; i++) {
            String plateStr = String.valueOf(plateNoChars[i]) +
                    plateNoChars[i + 1] +
                    plateNoChars[i + 2];
            plateStrList.add(plateStr);
        }
        log.info("车牌拆分结果：" + CollUtil.join(plateStrList, "、"));

        //根据集合中的字符串进行多次数据库模糊查询
        List<ParkCarEnterLog> enterLogLikeList = sdkIdentifyResultPushLogMapper.getParkCarEnterLogLikeList(plateStrList, parkCode, "", "", "", "", "");

        //未匹配到入场数据
        if (ObjectUtils.isEmpty(enterLogLikeList)) {
            return true;
        }

        //遍历查询结果，得到匹配车牌号，根据相似度算法获得与当前出场车牌的相似度
        List<SimilarityRatioParkCarEnterLog> similarityCarEnterLogs = new ArrayList<>();
        for (ParkCarEnterLog parkCarEnterLog : enterLogLikeList) {
            //获取相似度(0-100) 结果100则为完全一样
            int similarityRatio;
            if (parkCarEnterLog.getPlateNo().contains(plateNo)) {
                similarityRatio = 100;
            } else {
                similarityRatio = (int) similarityRatioAlgorithm.getSimilarityRatio(plateNo, parkCarEnterLog.getPlateNo());
            }

            //相似度小于50直接抛弃该数据
            if (similarityRatio > minSimilarityRatio) {
                SimilarityRatioParkCarEnterLog similarityCarEnterLog = new SimilarityRatioParkCarEnterLog();
                BeanUtils.copyProperties(parkCarEnterLog, similarityCarEnterLog);
                similarityCarEnterLog.setSimilarityRatio(similarityRatio);
                similarityCarEnterLogs.add(similarityCarEnterLog);
            }
        }
        return similarityCarEnterLogs.isEmpty();

    }

    /**
     * 车辆无入场记录处理逻辑
     *
     * @throws Exception
     */
    public boolean noEnterInfo(SdkIdentifyResultPushLog identifyResultPushLog,
                               ParkArmInfo parkArmInfo, List<ParkCarMonthly> parkCarMonthlyList) throws Exception {
        log.info("车辆无入场记录处理传入参数1->{},传入参数2->{},传入参数3->{}", identifyResultPushLog, parkArmInfo, parkCarMonthlyList);
        boolean flag = false;
        String carNature = String.valueOf(CarNatureKind.TEMPORARY_CAR.value());
        // 查询月租车位是否已满如果满位则无如场记录为临时车
        boolean isEffePark = parkSpaceService.getParkSpaceListCount(identifyResultPushLog.getParkCode(),
                identifyResultPushLog.getLicense(), JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y, parkCarMonthlyList);
        if (isEffePark) {
            /**
             * 1、判断车辆在车场是否开通月租车如果开通月租车并且无入场记录配置的是多位多车直接放行提示月租车
             * <p>，一路顺风，可用日期<d>天
             * 2、如果是临时车则直接播报一路顺风
             * */
            // 查询月租车配置信息
            MonthlyCarConfig carConfig = this.getMonthlyCarConfig(identifyResultPushLog.getParkCode());
            if (carConfig != null &&
                    (InOutTypeKind.MULTIPLE_IN_MULTIPLE_OUT.value().equals(carConfig.getInOutType())
                            || InOutTypeKind.SINGLE_INPUT_MULTIPLE_OUTPUT.value().equals(carConfig.getInOutType()))) {
                carNature = String.valueOf(CarNatureKind.MONTHLY_CAR.value());
                flag = true;
            }
        } else {
            ConfigParkInfo configParkInfo = configParkInfoService.findConfigParkInfoByParkCode();
            // 储值车无入场纪录处理
            ParkCarStored storedCar = parkCarStoredMapper.getEntranceStored(identifyResultPushLog.getLicense(), "Y");
            if (Objects.nonNull(storedCar)){
                carNature = String.valueOf(CarNatureKind.STORED_CAR.value());
            }
            if (Objects.nonNull(storedCar) &&
                    JddConstant.StringNumber.STRING_TWO.equals(configParkInfo.getAdmissionRecordsCarStoredCar())){
                flag = true;
            }
            // 临时车无入场记录处理
            if (JddConstant.StringNumber.STRING_TWO.equals(configParkInfo.getAdmissionRecordsCar())
                    && String.valueOf(CarNatureKind.TEMPORARY_CAR.value()).equals(carNature)) {
                flag = true;
            }
        }
        if (flag) {
            // 月租车免费放行播报信息
            if (String.valueOf(CarNatureKind.MONTHLY_CAR.value()).equals(carNature) && parkCarMonthlyList.size() > 0) {
                this.noEnterAndOutMonthlyBroadcastInfo(parkCarMonthlyList.get(0), "0", parkArmInfo.getSerialNo(), identifyResultPushLog.getLicense());
            } else {
                // 临时车免费放行播报信息
                ledCallInterface.OutAbnormalCanAll(parkArmInfo.getSerialNo(), identifyResultPushLog.getLicense());
            }
            this.handle(identifyResultPushLog, carNature);
        }
        return flag;
    }

    /**
     * 月租车产生费用提示信息处理
     */
    private boolean monthlyPayTips(String payedFee, String serialNo, String plateNo,
                                   List<ParkCarMonthly> parkCarMonthlyList, Integer chargeDuration) {
        // 是月租车并且月租车已生效
        if (CollectionUtils.isNotEmpty(parkCarMonthlyList) &&
                new Date().after(parkCarMonthlyList.get(0).getStartTime())) {
            // 月租车未过期播报信息
            ParkCarMonthly parkCarMonthly = parkCarMonthlyList.get(0);
            this.noEnterAndOutMonthlyBroadcastInfo(parkCarMonthly, payedFee, serialNo, plateNo);
            return true;
        }
        // 产生费用处理
        if (!JddConstant.StringNumber.STRING_ZERO.equals(payedFee)) {
            ledCallInterface.OutPayParkingFeeAll(serialNo, plateNo);
        } else {
            ledCallInterface.FieldFeeAllOut(serialNo, plateNo,chargeDuration);
        }
        return true;
    }

    /**
     * 无入场记录的月租车出场播报信息
     */
    private void noEnterAndOutMonthlyBroadcastInfo(ParkCarMonthly parkCarMonthly, String payedFee, String serialNo, String plateNo) {
        String str = EnterAndExportTipsConstant.MONTHLY_CAR;
        // 月租车未过期
        if (new Date().before(parkCarMonthly.getEndTime())) {
            // 获取剩余天数
            long surplusDay = DateUtil.getDaySub(DateUtil.dateToString(new Date(), DateUtil.FORMAT_DATETIME_YYYY_MM_DD),
                    DateUtil.dateToString(parkCarMonthly.getEndTime(), DateUtil.FORMAT_DATETIME_YYYY_MM_DD)) + 1;
            // 播报信息
            str = "月租车" + plateNo + ",可用日期" + surplusDay + EnterAndExportTipsConstant.DAY;
        } else {
            // 月租车已过期
            str = str + "," + plateNo + ",已过期";
        }
        try {
            ledCallInterface.outMonthlyEntranceAll(serialNo, plateNo, str.trim());
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("月租车出场播报信息->{}", str);
    }

    /**
     * @param sdkIdentifyResultPushLog 识别结构体
     * @return
     * @Description 车辆出场记录保存
     * @author shangyue
     * @Date 2021/4/7
     */
    private void handle(SdkIdentifyResultPushLog sdkIdentifyResultPushLog, String carNature) throws IOException {
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        String serialNo = sdkIdentifyResultPushLog.getSerialNo();
        ParkCarOutLog parkCarOutLog = new ParkCarOutLog();
        //出入口配置信息
        ParkArmInfo parkArmInfo = parkArmInfoService.findParkArmInfoBySerialNo(sdkIdentifyResultPushLog.getSerialNo());
        //得到最新的一次入场记录
        ParkCarEnterLog carEnterLog = parkCarEnterLogService.getLastCarEnterLog(sdkIdentifyResultPushLog.getParkCode(), plateNo, JddConstant.ParkCarEnterLog.CAR_NO_OUT);
        if (carEnterLog != null) {
            this.isMonthly(carEnterLog);
            if (StringUtils.isNotBlank(carEnterLog.getParkNumber())) {

                RestTemplateSpaceVo restTemplateSpaceVo = new RestTemplateSpaceVo();
                restTemplateSpaceVo.setLatestParkCarEnterLogStr(JSONObject.toJSONString(carEnterLog));
                log.info("发送请求云端同步更新月租车车位停靠状态:{}", sendCloudSynSpace(restTemplateSpaceVo));
                //月租车处理，后面判断是否自动放行用
                carNature = JddConstant.MqttRecResultPushLog.IS_WHITE_ONE;
            }
            parkCarOutLog.setCarEnterId(carEnterLog.getBatchNumber());
        }
        parkCarOutLog.setId(SnowFlakeUtil.getId());
        parkCarOutLog.setParkCode(parkArmInfo.getParkCode());
        parkCarOutLog.setParkName(parkArmInfo.getParkName());
        parkCarOutLog.setPlateNo(plateNo);
        parkCarOutLog.setSerialNo(serialNo);
        parkCarOutLog.setCarNature(carNature);
        parkCarOutLog.setOuterType(1);
        parkCarOutLog.setCarType(String.valueOf(JddConstant.IntNumber.INT_ONE));
        parkCarOutLog.setCarOutTime(DateUtil.getTime());
        parkCarOutLog.setArmName(parkArmInfo.getArmName());
        parkCarOutLog.setImgPath(sdkIdentifyResultPushLog.getImg());
        parkCarOutLog.setPlateNoImgPath(sdkIdentifyResultPushLog.getPlateNoImg());
        parkCarOutLog.setOssImgPath(sdkIdentifyResultPushLog.getOssImg());
        parkCarOutLog.setIpAddr(parkArmInfo.getIpAddr());
        parkCarOutLog.setCreateTime(new Date());
        parkCarOutLog.setSynchronousStatus(JddConstant.StatusType.STATUS_N);
        parkCarOutLog.setFreeAllCoupon(sdkIdentifyResultPushLog.isFreeAllCoupon());
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        try {
            //加上工作站名和岗亭人账户
            if (ObjectUtil.isNotEmpty(workUser)) {
                parkCarOutLog.setWorkName(parkArmInfo.getWorkStationName());
                parkCarOutLog.setSysUserName(workUser);
                //交接班更新，这里只更新出场数
                parkHandOverLogService.updateHandOverAuto(parkCarOutLog, null, workUser, 0);
            }
        } catch (Exception e) {
            log.error("-----------------车辆出场交接班更新或者发送场内车辆详情失败----------------");
        }

         parkCarOutEntranceRecordService.assemblyCarOutEntranceRecord(parkCarOutLog);


        log.info("缓存当前车道已出场车牌");
        if (JddConstant.StringNumber.STRING_ZERO.equals(carNature)) {
            cacheUtil.put(CacheConstant.PARK_CAR_OUT, serialNo, plateNo);
        }
        parkCarOutLogMapper.insert(parkCarOutLog);
        parkCarEnterLogMapper.updateIsOut(plateNo);
        Map<String, Object> data = parkArmInfoService.getData(workUser);
        socketServer.sendMessageAll(JSONObject.toJSONString(data));
        try {
            asynRestTmplateUtils.saveParkCarOutLog(parkCarOutLog);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查询
     */
    @Override
    public boolean isMonthly(ParkCarEnterLog carEnterLog) {
        // 判断是否为月租车，如果是月租车则判断月租车是否需要转卡
        if (String.valueOf(CarNatureKind.MONTHLY_CAR.value()).equals(carEnterLog.getCarNature())) {
            return this.monthlyAppearanceV3(carEnterLog);
        } else {
            log.info("月租车离场，重置月租车位状态");
            parkSpaceService.updateParkStatusNullV3(carEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);
            this.syncCloudData(carEnterLog, null);
        }
        return false;
    }

    /**
     * @return
     * @Title: monthlyAppearanceV3
     * @Description:第三版月租车出场转卡逻辑
     * @author: zjf
     * @date 2021年11月17日
     * @version 1.0
     */
    public boolean monthlyAppearanceV3(ParkCarEnterLog carEnterLog) {
        // 查询月租车配置信息
        MonthlyCarConfig carConfig = this.getMonthlyCarConfig(carEnterLog.getParkCode());
        log.info("月租车配置信息->{}", JSON.toJSONString(carConfig));
        // 2、查询入场车辆是否为月租车
        List<ParkCarMonthly> entranceMonthlyInfo = parkCarMonthlyService.getAreaMonthlyCarInfo(
                carEnterLog.getAreaId(), carEnterLog.getPlateNo(),
                JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        log.info("查询入场车辆月租车信息->{}", JSON.toJSONString(entranceMonthlyInfo));
        if (CollectionUtils.isEmpty(entranceMonthlyInfo)) {
            return false;
        }
        String monthlyCardId = entranceMonthlyInfo.get(0).getMonthlyCardId();
        if (StringUtils.isBlank(monthlyCardId)) {
            return false;
        }
        // 查询月卡信息
        MonthlyCardAndPackageVo vo = monthlyCardInfoService.getCardInfoAndPackageInfo(monthlyCardId);
        log.info("月卡信息->{}", JSON.toJSONString(vo));
        if (vo == null) {
            return false;
        }
        // 判断月租套餐是否过期
        boolean flag = DateUtil.betweenDateTime(new Date(),
                vo.getBeginTime(),
                vo.getEndTime());
        // 判断月租车是否启用转卡功能
        if (flag && BooleanTypeKind.YES.value() == carConfig.getStartTransition()) {
            // 查询月租套餐信息
            if (this.transferCard(vo, carEnterLog) == null) {
                return false;
            }
        } else {
            // 月租车不启用转卡
            log.info("月租车离场，重置月租车位状态");
            parkSpaceService.updateParkStatusNullV3(carEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);
            this.syncCloudData(carEnterLog, null);
        }
        return true;
    }

    /**
     * 保存停车订单
     */
    private void saveParkOrderInfo(DiscountCouponDetail discountCouponDetail, ParkOrderInfoVO parkGetCarFeeLog, ParkArmInfo parkArmInfo) throws IOException {
        log.info("使用全免/免费通行券保存订单：券详情{},计费信息{}", JSONObject.toJSONString(discountCouponDetail), JSONObject.toJSONString(parkGetCarFeeLog));
        ParkOrderInfo parkOrderInfo = new ParkOrderInfo();
        // 费用
        BeanUtils.copyProperties(parkGetCarFeeLog, parkOrderInfo);
        parkOrderInfo.setPlateNo(parkGetCarFeeLog.getPlateNo());
        parkOrderInfo.setParkCode(parkGetCarFeeLog.getParkCode());
        //  进场时间
        parkOrderInfo.setEnterTime(parkGetCarFeeLog.getEnterTime());
        // 出场时间
        parkOrderInfo.setOutTime(DateUtils.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
        parkOrderInfo.setOrderStatus("2");
        parkOrderInfo.setOrderStatusDesc("使用免费时长券订单或全免券,discount_type{}" + discountCouponDetail.getDiscountType());
        parkOrderInfo.setParkName(parkGetCarFeeLog.getParkName());
        parkOrderInfo.setVersion(0);
        parkOrderInfo.setPayTime(DateUtils.dateToString(new Date(), "yyyyMMddHHmmss"));
        parkOrderInfo.setSourceType("2");
        parkOrderInfo.setOutTradeNo(null);
        parkOrderInfo.setBankDiscAmt("0");
        parkOrderInfo.setOrderId(SnowFlakeUtil.getId());
        parkOrderInfo.setMerId(null);
        parkOrderInfo.setPayType("4");
        parkOrderInfo.setCreateTime(new Date());
        parkOrderInfo.setPaymentAmt("0");
        parkOrderInfo.setArmName(parkArmInfo.getArmName());
        /*由于云端校验了优惠卷，进入该方法的都是可以使用优惠卷直接出场的车辆,所以优惠卷金额使用支付费用*/
        //优惠券金额
        parkOrderInfo.setCouponAmt(parkGetCarFeeLog.getChargeUnpaid());
        //原停车费用
        parkOrderInfo.setChargeDue(parkGetCarFeeLog.getChargeUnpaid());
        //已支付费用
        parkOrderInfo.setChargePaid(parkGetCarFeeLog.getChargeUnpaid());
        //订单总金额
        parkOrderInfo.setTotalAmt(parkGetCarFeeLog.getChargeUnpaid());
        //优惠立减金额
        parkOrderInfo.setMerDiscAmt(parkGetCarFeeLog.getChargeUnpaid());
        parkOrderInfo.setCouponUseFlag(JddConstant.DiscountCouponConstant.USAGE_STATUS_USED);
        /*使用优惠券*/
        parkOrderInfo.setCouponId(discountCouponDetail.getId());
        try {
            //加上工作站名和岗亭人账户
            String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
            if (ObjectUtil.isNotEmpty(workUser)) {
                //交接班更新,这里只更新钱数
                parkHandOverLogService.updateHandOverAuto(null, parkOrderInfo, workUser, 0);
//                JSONObject HandOverLog = new JSONObject();
//                HandOverLog.put("HandOverLog", parkHandOverLog);
//                socketServer.sendInfo(HandOverLog.toJSONString(), parkArmInfo.getUsedUserId());
                Map<String, Object> data = parkArmInfoService.getData(workUser);
                socketServer.sendMessageAll(JSONObject.toJSONString(data));
            }
        } catch (Exception e) {
            log.error("-----------------车辆出场交接班更新或者发送场内车辆详情失败----------------");
        }
        if (JddConstant.DiscountCouponConstant.COUPON_TYPE_ORDINARY == discountCouponDetail.getCouponType()) {
            String url = "";
            restTemplateUtils.post(url, discountCouponDetail, ResultVo.class);
        }
        log.info("使用优惠券保存订单：{}", JSONObject.toJSONString(parkOrderInfo));
        parkOrderInfoMapper.insert(parkOrderInfo);
        //使用优惠卷出场的订单发送到云端 用于微信推送出场纪录显示优惠金额
        String url = "";
        restTemplateUtils.post(url, parkOrderInfo, ResultVo.class);
    }

    /**
     * 场端调用云端删除前一辆车redis缓存
     *
     * @param serialNo
     * @return com.jdd.common.api.vo.ResultVo
     * @author jdd孙庆伟
     * @date 2021/9/6 11:11:03
     * @version 1.0
     */
    public ResultVo redisDelete(String serialNo) {
        String url = "";
        RedisVo redisVo = new RedisVo();
        redisVo.setSerialNo(serialNo);
        redisVo.setKey("park:cloud:out:fee:");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //封装参数
        MultiValueMap<String, String> stringStringSortedMap = EncapsulationSignDataUtils.encapsulationParameters(redisVo, appId, appScrect);
        // 组装请求体
        HttpEntity<MultiValueMap<String, String>> request =
                new HttpEntity<MultiValueMap<String, String>>(stringStringSortedMap, headers);
        ResponseEntity<ResultVo> cmsResultDTO = restTemplateUtils.post(url, request, ResultVo.class);
        return cmsResultDTO.getBody();
    }

    /**
     * 临时车入场处理 新版
     *
     * @param
     * @param
     */
    public boolean temporaryCar(ParkArmInfo parkArmInfo,
                                SdkIdentifyResultPushLog sdkIdentifyResultPushLog, MonthlyEnterTypeVo vo, String str) throws Exception {

        String serialNo = parkArmInfo.getSerialNo();
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        //获取车场配置信息
        ConfigParkInfo configParkInfo = configParkInfoService.findConfigParkInfoByParkCode();
        if (configParkInfo != null) {
            //车场临时车位数
            Integer temporaryParking = configParkInfo.getTemporaryParking();
            int parkCarEnterLogListCount = parkCarEnterLogMapper.getNoOutCarNumber();
            //临时车位已满情况处理
            if (JddConstant.ConfigParkInfo.TEMPORARY_STALL_TYPE_ONE.equals(configParkInfo.getTemporaryStallType())) {
                if (parkCarEnterLogListCount >= temporaryParking) {
                    log.info("车位已满，禁止驶入");
                    ledCallInterface.TemporaryEntranceParkingFullAll(serialNo, plateNo);
                    return true;
                }
            }
            //临时车位未满情况处理
            temporaryVehicle(serialNo, plateNo, configParkInfo, sdkIdentifyResultPushLog, parkArmInfo, vo, str);
            return true;
        }
        ledCallInterface.TemporaryEntranceAll(serialNo, plateNo);
        saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO, false, null);
        return true;

    }

    /**
     * 临时车入场判断条件
     *
     * @param serialNo                 设备序列号
     * @param plateNo                  车牌号
     * @param configParkInfo           车场配置
     * @param sdkIdentifyResultPushLog 车辆识别结果
     * @param parkArmInfo              车道信息
     */
    private boolean temporaryVehicle(String serialNo, String plateNo,
                                     ConfigParkInfo configParkInfo, SdkIdentifyResultPushLog sdkIdentifyResultPushLog,
                                     ParkArmInfo parkArmInfo, MonthlyEnterTypeVo vo, String str) throws Exception {
        /**
         *临时车入场开闸方式 1 自动开闸
         */
        if (configParkInfo.getTemporaryGateIn().equals(JddConstant.ConfigParkInfo.TEMPORARY_GATE_IN_ONE)) {
            if (str.isEmpty()){
                ledCallInterface.TemporaryEntranceAll(parkArmInfo.getSerialNo(), sdkIdentifyResultPushLog.getLicense());
            }else {
                ledCallInterface.EntranceParkWhiteOverAll(parkArmInfo.getSerialNo(), sdkIdentifyResultPushLog.getLicense());
            }
            ParkCarEnterLog parkCarEnterLog = saveParkCarEnterLog(sdkIdentifyResultPushLog,
                    parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO, vo.isTransferCard(), null);
            if (parkCarEnterLog != null && vo.getPreTransfer() != null &&
                    vo.getPreTransfer() == JddConstant.IntNumber.INT_ONE
                    && StringUtils.isNotBlank(vo.getParkNumber())) {
                // 保存预转卡记录
                ParkCarEnterLog carEnterLog = parkCarEnterLogService.saveTransferCardInfo(parkCarEnterLog, vo);
                // 将获得的车位的停车状态改为有车辆停靠
            	parkSpaceService.updateParkStatusV3(carEnterLog, JddConstant.ParkSpace.PARKSTATUS_ONE,carEnterLog.getPlateNo());
                //发送webSocket消息 并上传云端入场记录
                asynRestTmplateUtils.saveParkCarEnterLogV3(carEnterLog);
                this.sendMessage(carEnterLog);
            }
            return true;
        }
        /*
         *临时车入场开闸方式 2 确认开闸
         */
        if (configParkInfo.getTemporaryGateIn().equals(JddConstant.ConfigParkInfo.TEMPORARY_GATE_IN_TWO)) {
            log.info("临时车入场，确认开闸");
            ledCallInterface.TemporaryInConfigAll(serialNo, plateNo);
            //发送webSockt消息到前端
            sendInfo(serialNo, plateNo, parkArmInfo, JddConstant.VehicleType.TEMPORARYCAR, "0", sdkIdentifyResultPushLog.getImg());
            return true;
        }
        /*
         *临时车入场开闸方式 3、不开闸
         */
        if (configParkInfo.getTemporaryGateIn().equals(JddConstant.ConfigParkInfo.TEMPORARY_GATE_IN_THREE)) {
            log.info("临时车入场，不开闸");
            ledCallInterface.TemporaryBanEntranceAll(serialNo, plateNo);
            return true;
        }
        return false;
    }


    /**
     * 储值车入场处理
     *
     * @param parkArmInfo              出入口配置信息
     * @param sdkIdentifyResultPushLog 相机识别结果
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/6 10:18:29
     * @version 1.0
     */
    private boolean carStored(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog sdkIdentifyResultPushLog) throws Exception {
        String serialNo = parkArmInfo.getSerialNo();
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        ConfigParkInfo configParkInfo = configParkInfoService.findConfigParkInfoByParkCode();
        if (configParkInfo != null) {
            //自动开闸情况
            if (configParkInfo.getMonthlyRentalGateIn().equals(JddConstant.ConfigParkInfo.MONTHLY_RENTAL_GATE_IN_ONE)) {
                log.info("储值车:{}", plateNo + "入场自动开闸");
                ledCallInterface.EntranceStoredInConfigAll(serialNo, plateNo);
                saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_TWO);
                return true;
            }
            //确认开闸
            if (configParkInfo.getMonthlyRentalGateIn().equals(JddConstant.ConfigParkInfo.MONTHLY_RENTAL_GATE_IN_TWO)) {
                log.info("储值车:{}", plateNo + "入场确认开闸");
                //发送webSockt消息到前端
                sendInfo(sdkIdentifyResultPushLog.getSerialNo(), sdkIdentifyResultPushLog.getLicense(), parkArmInfo,
                        JddConstant.VehicleType.STOREDCAR, "0", sdkIdentifyResultPushLog.getImg());
                ledCallInterface.StoredInConfigAll(serialNo, plateNo);
                return true;
            }
        }
        log.info("储值车:{}", plateNo + "入场成功");
        ledCallInterface.EntranceStoredNoConfigAll(serialNo, plateNo);
        saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_TWO);
        return true;
    }

    /**
     * 车辆入场保存
     *
     * @param sdkIdentifyResultPushLog 车辆识别信息
     * @param parkArmInfo              停车场出入口配置信息
     * @param isWhite                  0，临时车，1，月租车，2，储值车，3，军警车"
     * @throws IOException
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public ParkCarEnterLog saveParkCarEnterLog(SdkIdentifyResultPushLog sdkIdentifyResultPushLog,
                                               ParkArmInfo parkArmInfo, String isWhite,
                                               boolean isTransferCard, String monthlyCardId) throws IOException {
        ParkCarEnterLog carEnterLog = new ParkCarEnterLog();
        carEnterLog.setId(SnowFlakeUtil.getId());
        // 大车场入口-->入场记录中入场批次号是雪花算法生产的值
        carEnterLog.setBatchNumber(carEnterLog.getId());
        if (StringUtils.isNotBlank(parkArmInfo.getSourceAreaId())) {
            List<ParkCarEnterLog> carEnterLogList = getParkCarEnterLogList(sdkIdentifyResultPushLog.getLicense());
        	if (CollectionUtils.isEmpty(carEnterLogList)) {
                log.error("车辆无大场入场记录，入场将不能正常计费，禁止驶入小车场");
                ledCallInterface.OutAbnormalBanAll(parkArmInfo.getSerialNo(), sdkIdentifyResultPushLog.getLicense());
                return null;
            }
            ParkCarEnterLog lastCarEnterLog = carEnterLogList.get(0);
            if (StringUtils.isNotBlank(lastCarEnterLog.getParkNumber()) && !isTransferCard) {
                log.info("月租车切换停驶车位,车位编号：{}", lastCarEnterLog.getParkNumber());
                parkSpaceService.updateParkStatusNullV3(lastCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);
            }
            // 小车场入口-->入场记录中入场批次号存最近入场记录的车位号
            carEnterLog.setBatchNumber(lastCarEnterLog.getBatchNumber());
            // 处理临时车重复入场
            for(ParkCarEnterLog newCarEnterLog : carEnterLogList){
            	if (this.isRepeatOut(newCarEnterLog, parkArmInfo)
            			&& !String.valueOf(CarNatureKind.MONTHLY_CAR.value()).equals(isWhite)) {
            		log.info("临时车重复入场记录{}", newCarEnterLog);
            		// 更新重复入场状态
            		newCarEnterLog.setEnterType(JddConstant.IntNumber.INT_ONE);
            		newCarEnterLog.setIsOut(JddConstant.StringNumber.STRING_ONE);
            		newCarEnterLog.setThisArea(JddConstant.IntNumber.INT_ZERO);
            		//parkCarEnterLogService.updateById(newCarEnterLog);
            		parkCarEnterLogMapper.updateAreaIdIsOut(sdkIdentifyResultPushLog.getLicense(),parkArmInfo.getSourceAreaId());
            		//临时车入场记录上传到云端
            		ParkCarEnterLogSyncV3Vo entity = new ParkCarEnterLogSyncV3Vo();
            		entity.setParkCarEnterLogStr(JSON.toJSONString(newCarEnterLog));
            		entity.setBatchNumber(newCarEnterLog.getBatchNumber());
                    String url = "";
                    asynRestTmplateUtils.monthlyCarV3(entity, url);
            		break;
            	}
            }
            // 将要转卡的记录在当前区域停靠状态改为未停靠
            parkCarEnterLogService.updateCarEnterLogThisArea(parkArmInfo.getSourceAreaId(), sdkIdentifyResultPushLog.getLicense());
        } else {
        	// 查询临时车入场记录
        	List<String> batchNumberList = parkCarEnterLogMapper.selectByAreaIdAndPlateNoList(parkArmInfo.getTargetAreaId(), sdkIdentifyResultPushLog.getLicense());
            // 大车场入口-->入场记录中入场批次号是雪花算法生产的值
            //有入大车场但未出场的入场记录，有则异常并记录
            parkCarOutAbnormalLogService.findAbnormals(parkArmInfo.getTargetAreaId(), sdkIdentifyResultPushLog.getLicense());
            parkCarEnterLogMapper.updateIsOut(sdkIdentifyResultPushLog.getLicense());

            if(CollectionUtils.isNotEmpty(batchNumberList)){
            	//临时车入场记录上传到云端
            	ParkCarEnterLog newCarEnterLog = new ParkCarEnterLog();
            	newCarEnterLog.setAreaId(parkArmInfo.getTargetAreaId());
            	newCarEnterLog.setPlateNo(sdkIdentifyResultPushLog.getLicense());
            	newCarEnterLog.setIsOut(JddConstant.StringNumber.STRING_ONE);
            	newCarEnterLog.setEnterType(JddConstant.IntNumber.INT_ONE);
            	newCarEnterLog.setThisArea(JddConstant.IntNumber.INT_ZERO);
            	ParkCarEnterLogSyncV3Vo entity = new ParkCarEnterLogSyncV3Vo();
            	entity.setParkCarEnterLogStr(JSON.toJSONString(newCarEnterLog));
            	entity.setBatchNumber(batchNumberList.get(0));
                String url = "";
                asynRestTmplateUtils.monthlyCarV3(entity, url);
            	carEnterLog.setBatchNumber(carEnterLog.getId());
            }
        }
        carEnterLog.setAreaId(parkArmInfo.getTargetAreaId());
        carEnterLog.setParkCode(parkArmInfo.getParkCode());
        carEnterLog.setParkName(parkArmInfo.getParkName());
        carEnterLog.setSerialNo(parkArmInfo.getSerialNo());
        carEnterLog.setIpAddr(parkArmInfo.getIpAddr());
        carEnterLog.setCarEnterTime(DateUtil.newDate());
        carEnterLog.setPlateNo(sdkIdentifyResultPushLog.getLicense());
        carEnterLog.setCarTypeNo(sdkIdentifyResultPushLog.getNType() + "");
        carEnterLog.setImgPath(sdkIdentifyResultPushLog.getImg());
        carEnterLog.setOssImgPath(sdkIdentifyResultPushLog.getOssImg());
        carEnterLog.setCarType(StringUtils.isNotBlank(sdkIdentifyResultPushLog.getLicense()) ? JddConstant.ParkCarEnterLog.VEHICLE : JddConstant.ParkCarEnterLog.UNVEHICLE);
        carEnterLog.setIsOut(JddConstant.StringNumber.STRING_ZERO);

        carEnterLog.setArmName(parkArmInfo.getArmName());
        carEnterLog.setCarNature(isWhite);
        carEnterLog.setCreateTime(new Date());
        carEnterLog.setIsTransfer(JddConstant.StringNumber.STRING_ZERO);
        carEnterLog.setEnterType(sdkIdentifyResultPushLog.getEnterType());
        carEnterLog.setCarTypeId(sdkIdentifyResultPushLog.getCarTypeId());
        carEnterLog.setCarTypeName(sdkIdentifyResultPushLog.getCarTypeName());
        carEnterLog.setThisArea(JddConstant.IntNumber.INT_ONE);
        carEnterLog.setMonthlyCardId(monthlyCardId);
        carEnterLog.setEnterType(JddConstant.IntNumber.INT_ZERO);
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtil.isNotEmpty(workUser)) {
            carEnterLog.setWorkName(parkArmInfo.getWorkStationName());
            carEnterLog.setSysUserName(workUser);
        }

        if (StringUtils.isBlank(parkArmInfo.getSourceAreaId())) {
            parkCarOutEntranceRecordService.assemblyCarEntranceRecord(carEnterLog);
        }
        parkCarEnterLogService.save(carEnterLog);
        //发送webSocket消息 并上传云端入场记录

        asynRestTmplateUtils.saveParkCarEnterLog(carEnterLog);
        this.sendMessage(carEnterLog);
        return carEnterLog;
    }

    /**
     * 车辆入场保存
     *
     * @param sdkIdentifyResultPushLog 车辆识别信息
     * @param parkArmInfo              停车场出入口配置信息
     * @param isWhite                  0，临时车，1，月租车，2，储值车，3，军警车"
     */
    private void saveParkCarEnterLog(SdkIdentifyResultPushLog sdkIdentifyResultPushLog, ParkArmInfo parkArmInfo, String isWhite) throws Exception {
        ParkCarEnterLog carEnterLog = new ParkCarEnterLog();
        if (StringUtils.isNotBlank(sdkIdentifyResultPushLog.getCarTypeId())) {
            carEnterLog.setCarTypeId(sdkIdentifyResultPushLog.getCarTypeId());
            carEnterLog.setCarTypeName(sdkIdentifyResultPushLog.getCarTypeName());
            carEnterLog.setThisArea(JddConstant.IntNumber.INT_ONE);
        }
        //组装车辆入场记录
        packageCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, carEnterLog, DateUtil.newDate(), isWhite);
        if (StringUtils.isNotBlank(parkArmInfo.getSourceAreaId())) {
            List<ParkCarEnterLog> carEnterLogList = getParkCarEnterLogList(sdkIdentifyResultPushLog.getLicense());
            if (CollectionUtil.isNotEmpty(carEnterLogList)) {
                ParkCarEnterLog lastCarEnterLog = carEnterLogList.get(0);
                //以前有入场记录，而且又是月租车，则把原来的入场记录的月租车车位改成未停靠
                if (StringUtils.isNotBlank(lastCarEnterLog.getParkNumber())) {
                    parkSpaceService.updateParkStatus(lastCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);

                    //发送HTTP请求云端同步更新月租车位状态
                    RestTemplateSpaceVo restTemplateSpaceVo = new RestTemplateSpaceVo();
                    restTemplateSpaceVo.setLatestParkCarEnterLogStr(JSONObject.toJSONString(lastCarEnterLog));
                    log.info("发送请求到云端同步更新月租车车位停靠状态:{}", sendCloudSynSpace(restTemplateSpaceVo));
                }
                // 小车场入口-->入场记录中入场批次号存最近入场记录的车位号
                carEnterLog.setBatchNumber(lastCarEnterLog.getBatchNumber());
            }
        } else {
            // 大车场入口-->入场记录中入场批次号是雪花算法生产的值
            //有入大车场但未出场的入场记录，有则异常并记录
            parkCarOutAbnormalLogService.findAbnormals(parkArmInfo.getTargetAreaId(), sdkIdentifyResultPushLog.getLicense());
            parkCarEnterLogMapper.updateIsOut(sdkIdentifyResultPushLog.getLicense());
            carEnterLog.setBatchNumber(carEnterLog.getId());
            //组装停车记录表
            parkCarOutEntranceRecordService.assemblyCarEntranceRecord(carEnterLog);
        }
        parkCarEnterLogService.save(carEnterLog);
        //发送webSocket消息 并上传云端入场记录
        asynRestTmplateUtils.saveParkCarEnterLog(carEnterLog);

        sendMessage(carEnterLog);
    }







    /**
     * 根据相机识别日志和车道信息组装车辆入场记录
     *
     * @param identifyResultPushLog 相机识别日志
     * @param parkArmInfo           车道信息
     * @param carEnterTime          车辆入场时间
     * @return 车辆入场记录
     */

    private ParkCarEnterLog packageCarEnterLog(SdkIdentifyResultPushLog identifyResultPushLog,
                                               ParkArmInfo parkArmInfo, ParkCarEnterLog saveCarEnterLog, String carEnterTime, String isWhite) {
        if (null == saveCarEnterLog) {
            saveCarEnterLog = new ParkCarEnterLog();
        }

        saveCarEnterLog.setId(SnowFlakeUtil.getId());
        saveCarEnterLog.setParkCode(parkArmInfo.getParkCode());
        saveCarEnterLog.setParkName(parkArmInfo.getParkName());
        saveCarEnterLog.setAreaId(parkArmInfo.getTargetAreaId());
        saveCarEnterLog.setArmName(parkArmInfo.getArmName());
        saveCarEnterLog.setSerialNo(parkArmInfo.getSerialNo());
        saveCarEnterLog.setIpAddr(parkArmInfo.getIpAddr());
        saveCarEnterLog.setPlateNo(identifyResultPushLog.getLicense());
        saveCarEnterLog.setCarEnterTime(carEnterTime);
        saveCarEnterLog.setImgPath(identifyResultPushLog.getImg());
        if (null != identifyResultPushLog.getOssImg()) {
            saveCarEnterLog.setOssImgPath(identifyResultPushLog.getOssImg());
        }
        saveCarEnterLog.setIsOut("0");
        saveCarEnterLog.setCarNature(isWhite);
        saveCarEnterLog.setCarTypeNo(String.valueOf(identifyResultPushLog.getNType()));
        saveCarEnterLog.setCarType(null != identifyResultPushLog.getLicense() ? MoblieControllerUtils.HAVEPLATE : MoblieControllerUtils.NONEPLATE);
        saveCarEnterLog.setCreateTime(new Date());
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtil.isNotEmpty(workUser)) {
            saveCarEnterLog.setWorkName(parkArmInfo.getWorkStationName());
            saveCarEnterLog.setSysUserName(workUser);
        }
        saveCarEnterLog.setIsTransfer(JddConstant.StringNumber.STRING_ZERO);
        saveCarEnterLog.setEnterType(JddConstant.IntNumber.INT_ZERO);
        return saveCarEnterLog;
    }


    /**
     * 发送请求云端同步月租车位状态
     *
     * @param restTemplateSpaceVo
     * @return com.jdd.common.api.vo.ResultVo
     * @author jdd孙庆伟
     * @date 2021/10/15 14:15:05
     * @version 1.0
     */
    public ResultVo sendCloudSynSpace(RestTemplateSpaceVo restTemplateSpaceVo) {
        String url = "";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //封装参数
        MultiValueMap<String, String> stringStringSortedMap = EncapsulationSignDataUtils.encapsulationParameters(restTemplateSpaceVo, appId, appScrect);
        // 组装请求体
        HttpEntity<MultiValueMap<String, String>> request =
                new HttpEntity<MultiValueMap<String, String>>(stringStringSortedMap, headers);
        ResponseEntity<ResultVo> cmsResultDTO = restTemplateUtils.post(url, request, ResultVo.class);
        return cmsResultDTO.getBody();
    }

    /**
     * 发送webSocket消息
     *
     * @param parkCarEnterLog
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 17:37:02
     * @version 1.0
     */
    @Override
    public void sendMessage(ParkCarEnterLog parkCarEnterLog) throws IOException {
        //webSocket发送监控中心数据信息
        ParkArmInfo parkArmInfo = parkArmInfoService.findParkArmInfoBySerialNo(parkCarEnterLog.getSerialNo());
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        Map<String, Object> data = parkArmInfoService.getData(workUser);
        socketServer.sendMessageAll(JSONObject.toJSONString(data));
    }

    /**
     * 发送webSocket消息 并上传云端入场记录
     *
     * @param entity
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 17:37:02
     * @version 1.0
     */
    @Override
    public void sendMessageV3(ParkCarEnterLogSyncV3Vo entity,String url) throws IOException {
        //webSocket发送监控中心数据信息
        Map<String, Object> data = parkArmInfoService.getData(null);
        socketServer.sendMessageAll(JSONObject.toJSONString(data));
        if(entity != null && entity.getParkCarEnterLogStr() != null){
        	//上传云端 图片路径修改成oss图片路径
        	ParkCarEnterLog parkCarEnterLog = JSON.parseObject(entity.getParkCarEnterLogStr(), ParkCarEnterLog.class);
        	parkCarEnterLog.setImgPath(parkCarEnterLog.getOssImgPath());
        	entity.setParkCarEnterLogStr(JSON.toJSONString(parkCarEnterLog));
        	//云端保存结果 成功200 失败 400
        	String remoteResults = getRemoteResults(url, entity);
        	//如果云端保存出场记录成功则修改场端出场记录 是否同步成功状态
        	if (remoteResults.equals(JddConstant.SaveCloudType.SAVE_SUCCESS) &&
        			StringUtils.isNotBlank(parkCarEnterLog.getId())) {
        		parkCarEnterLogMapper.editSynchronousStatus(parkCarEnterLog.getId(), remoteResults);
        	}
        }
    }

    /**
     * 入场记录
     *
     * @param plateNo 车牌号
     * @return
     */
    private List<ParkCarEnterLog> getParkCarEnterLogList(String plateNo) {
        QueryWrapper<ParkCarEnterLog> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("plate_no", plateNo);
        queryWrapper.eq("is_out", JddConstant.StringNumber.STRING_ZERO);
        queryWrapper.orderByDesc("car_enter_time");
        return parkCarEnterLogService.list(queryWrapper);
    }

    /**
     * 获得云端出/入场记录是否保存成功
     *
     * @param url    远程访问接口
     * @param object 远程传递参数
     * @return java.lang.String
     * @author jdd孙庆伟
     * @date 2021/5/28 15:25:57
     * @version 1.0
     */
    public String getRemoteResults(String url, Object object) {
        //调用云端入场记录保存
        int restTemplateCode = (int) GuavaRetryingUtils.retrySend(object, url, restTemplateUtils, appId, appScrect);
        //restTemplateCode为200则云端保存成功 restTemplateCode为400则云端保存失败
        return ResultVo.SUCCESS_CODE == restTemplateCode ? JddConstant.SaveCloudType.SAVE_SUCCESS : JddConstant.SaveCloudType.SAVE_ERROR;
    }

    /**
     * 车场入场确认开闸 发送webSockt消息 车场监控确认开闸
     *
     * @param serialNo
     * @param plateNo
     * @param parkArmInfo
     * @param vehicleType
     * @param imgPath     入口相机抓拍图片
     * @return void
     * @author jdd孙庆伟
     * @date 2021/8/28 15:40:15
     * @version 1.0
     */
    public void sendInfo(String serialNo, String plateNo, ParkArmInfo parkArmInfo, String vehicleType, String parkingIsFull, String imgPath) {
        try {
            WebSocketVo webSocketVo = new WebSocketVo();
            webSocketVo.setPlateNo(plateNo);
            webSocketVo.setVehicleType(vehicleType);
            webSocketVo.setArmName(parkArmInfo.getArmName());
            webSocketVo.setArmType(parkArmInfo.getArmType());
            webSocketVo.setSerialNo(serialNo);
            webSocketVo.setArmId(parkArmInfo.getId());
            webSocketVo.setParkingIsFull(parkingIsFull);
            webSocketVo.setImgPath(imgPath);
            webSocketVo.setCarEnterTime(DateUtil.fomatDateTimeStr(new Date()));
            socketServer.sendInfo(JSONObject.toJSONString(webSocketVo), parkArmInfo.getUsedUserId());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 欢迎光临播报显示
     *
     * @param identifyResultPushLog 识别结果
     * @param parkArmInfo           车道信息
     * @param serialNo              序列号
     * @param plateNo               车牌号
     * @param carNature             车辆类型
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/3 11:28:08
     * @version 1.0
     */
    private boolean welcomeDeal(SdkIdentifyResultPushLog identifyResultPushLog, ParkArmInfo parkArmInfo, String serialNo, String plateNo, String carNature) throws Exception {
        ledCallInterface.EntranceSpecialAll(serialNo, plateNo);
        saveParkCarEnterLog(identifyResultPushLog, parkArmInfo, carNature);
        log.info("军警车车辆，直接通行");
        return true;
    }

    private boolean welcomeWhiteDeal(SdkIdentifyResultPushLog identifyResultPushLog, ParkArmInfo parkArmInfo, String serialNo, String plateNo, String carNature) throws Exception {
        ledCallInterface.EntranceWhite(serialNo, plateNo);
        saveParkCarEnterLog(identifyResultPushLog, parkArmInfo, carNature);
        log.info("白名单车辆，未过期通行");
        return true;
    }

    /**
     * 通过设备序列号、停车场编号、车辆类型配置id 获取停车场通行配置信息
     *
     * @param serialNo    设备序列号
     * @param carSmTypeId 车辆类型配置id
     * @return java.lang.Boolean
     * @author jdd孙庆伟
     * @date 2021/5/26 18:31:23
     * @version 1.0
     */
    public Boolean ifPassage(String serialNo, String carSmTypeId, String plateNo) throws Exception {
        ConfigParkThrough configParkThrough = parkThroughService.queryConfigParkThrough(serialNo);
        //如果存在通行配置则进入通行处理
        if (null != configParkThrough) {
            //获取车场通行配置信息 车辆类型id
            String carTypeId = configParkThrough.getCarTypeId();
            //判断当前车辆类型是否在车场通行配置
            boolean str = strContains(carTypeId, carSmTypeId);
            if (str) {
                //获取车辆类型禁行日
                return !judgeStopDay(configParkThrough, serialNo, plateNo);
            }
        }
        return true;
    }

    /**
     * 匹配车辆类型
     *
     * @param str       车辆类型配置id(多个按逗号分开)
     * @param carTypeId 当前车辆类型id
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/6 10:40:00
     * @version 1.0
     */
    private boolean strContains(String str, String carTypeId) {
        String[] split = str.split(",");
        List<String> list = new ArrayList<>();
        Collections.addAll(list, split);
        return list.contains(carTypeId);
    }

    /*
     * @Description 判断车辆是否在禁行日或者禁行时段内
     * @author shangyue
     * @Date 2021/3/31
     * @param configParkThrough
     * @param serialNo
     * @return
     */
    private boolean judgeStopDay(ConfigParkThrough configParkThrough, String serialNo, String plateNo) throws Exception {
        String stopWeeklyDay = configParkThrough.getStopWeeklyDay();
        long startTime = DateUtil.newLong(configParkThrough.getStopStartTime());
        long endTime = DateUtil.newLong(configParkThrough.getStopEndTime());
        long newTime = DateUtil.newLong(DateUtil.newTime());
        if (!getWeek(stopWeeklyDay)) {
            if (newTime >= startTime && newTime <= endTime) {
                ledCallInterface.JudgeStopDayAll(serialNo, plateNo);
                return true;
            }
        }
        //没有选择星期数，只选择了时间,且开始时间小时数大于结束时间小时数则为跨天
        if (org.apache.commons.lang.StringUtils.isEmpty(stopWeeklyDay) && org.apache.commons.lang.StringUtils.isNotEmpty(configParkThrough.getStopStartTime())
                && org.apache.commons.lang.StringUtils.isNotEmpty(configParkThrough.getStopEndTime()) && startTime > endTime){
            if (newTime >= startTime || newTime <= endTime) {
                ledCallInterface.JudgeStopDayAll(serialNo, plateNo);
                return true;
            }
        }
        return false;
    }

    private boolean getWeek(String stopWeeklyDay) {
        if (null == stopWeeklyDay) {
            return true;
        }
        String[] split = stopWeeklyDay.split(",");
        List<String> str = new ArrayList<>();
        Collections.addAll(str, split);
        String week = null;
        Date today = new Date();
        Calendar c = Calendar.getInstance();
        c.setTime(today);
        int weekday = c.get(Calendar.DAY_OF_WEEK);
        if (weekday == 1) {
            week = "7";
        } else if (weekday == 2) {
            week = "1";
        } else if (weekday == 3) {
            week = "2";
        } else if (weekday == 4) {
            week = "3";
        } else if (weekday == 5) {
            week = "4";
        } else if (weekday == 6) {
            week = "5";
        } else if (weekday == 7) {
            week = "6";
        }
        return !str.contains(week);
    }

    /**
     * 车辆类型比对
     *
     * @param type 车辆类型
     * @return java.lang.Boolean
     * @author jdd孙庆伟
     * @date 2021/9/3 11:29:19
     * @version 1.0
     */
    private Boolean freeIf(String type) {
        List<String> list = CarType.getFreeEnum();
        return list.contains(type);
    }

    /**
     * 根据车辆编号、车牌类型匹配停车场的车辆类型Id
     *
     * @param type 车牌类型
     * @return java.lang.String
     * @version 1.0
     */
    private String getCarType(int type) {
        String result = null;
        List<ConfigCarType> list = carTypeService.getCarTypeList();
        for (ConfigCarType carType : list) {
            String cardTypeId = carType.getId();
            List<String> typeNo = Arrays.asList(carType.getCarTypeNo().split(","));
            if (typeNo.contains(type + "")) {
                result = cardTypeId;
                return result;
            }
        }
        return result;
    }

    /**
     * 保存停车订单
     *
     * @param parkArmInfo
     * @param identifyResultPushLog
     * @param carTypeIds
     * @param plateNo
     * @param fee
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author jdd孙庆伟
     * @date 2021/5/8 10:01:33
     * @version 1.0
     */
    public Map<String, Object> generateParkOrderInfoVO(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog identifyResultPushLog, String carTypeIds, String plateNo, long fee) {
        Map<String, Object> data = new HashMap<>(16);
        ParkOrderInfoVO parkOrderInfoVO = new ParkOrderInfoVO();
        String carType = getCarType(Integer.parseInt(carTypeIds));
        parkOrderInfoVO.
                setId(SnowFlakeUtil.getId()).
                setIpAddr(parkArmInfo.getIpAddr()).
                setSerialNo(parkArmInfo.getSerialNo()).
                setParkCode(parkArmInfo.getParkCode()).
                setParkName(parkArmInfo.getParkName()).
                setOutImgPath(identifyResultPushLog.getOssImg()).
                setCarTypeId(carType).
                setType(identifyResultPushLog.getNType().toString()).
                setPlateNo(plateNo).
                setChargeUnpaid(fee + "").
                setChargePaid("0").
                setChargeDue("0").
                setArmName(parkArmInfo.getArmName()).
                setChargeTotalFee(fee + "").
                setOrderStatus("1");
        data.put("parkOrderInfo", parkOrderInfoVO);
        return data;
    }

    /**
     * 判断当前时间是否在[startTime, endTime]区间，注意时间格式要一致
     *
     * @param nowTime   当前时间
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return
     * @author jqlin
     */
    public static boolean isEffectiveDate(Date nowTime, Date startTime, Date endTime) {
        if (nowTime.getTime() == startTime.getTime()
                || nowTime.getTime() == endTime.getTime()) {
            return true;
        }

        Calendar date = Calendar.getInstance();
        date.setTime(nowTime);

        Calendar begin = Calendar.getInstance();
        begin.setTime(startTime);

        Calendar end = Calendar.getInstance();
        end.setTime(endTime);

        return date.after(begin) && date.before(end);
    }
}
